ls -l > foo.txt
再通过 cat foo.txt 查看文件内容可以看到 foo.txt 的文件大小是 0
也就是说,先生成了文件,然后再执行 ls 命令,然后再将内容输出到文件里
请问一下这是什么原理?
1
zhlxsh 2023-04-20 09:53:55 +08:00 via iPhone
这不就是从左到右吗?先执行 ls ,得到的结果是 foo 大小 0 。然后将结果写到 foo
|
2
ysc3839 2023-04-20 10:04:40 +08:00 via Android
因为“重定向 stdout 到文件”这个操作就是把新进程的“file descriptor 1”设置成目标文件,设置成目标文件之前要先打开目标文件,打开目标文件前要先创建目标文件,所以就先有了目标文件。
|
3
AoEiuV020CN 2023-04-20 10:06:50 +08:00
关键应该是 fork exec 重定向 相关逻辑,
c 语言入门前几课,fopen 参数 w 就是直接先创建空文件的, 重定向是在 exec 执行 ls 进程前 shell 就要先给准备好 012 三个标准 io , > foo.txt 意味着 1 号是 w 方式打开 foo.txt ,这时候就创建文件了, 另外 ls 的输出会被缓冲,读取文件信息再到写入,这个过程文件本身还是空的, 等真正写入文件大小变化的时候 ls 早已经读取完文件信息了, |
4
asilin 2023-04-20 10:16:48 +08:00 3
所以这就是为什么不能使用“grep 有效信息 a.txt > a.txt”的方式进行原地文本内容替换的原因了。
因为 bash 首先会将 a.txt 文件清空,然后再使用 grep 查询,那结果当然是 a.txt 文件被毁掉了。 很多刚入门 Linux 的初学者,会经常性或者直觉性的犯一些这样的错误。 类似的还有"ls a b c | grep" 这种命令,注意“|” 只接收标准输出,而不接收标准错误,如果要将管道前面的所有输出(包括错误信息)给到 grep 命令,那就得使用"|&"来代替"|"才能实现。 诸如此类的知识,在 bash 的 man 文档和 info 文档中具有体现和介绍,所以推荐学习 bash 和 shell 最好看官方文档。 |
5
julyclyde 2023-04-20 11:13:54 +08:00
@AoEiuV020CN 跟输出缓冲没啥关系。“先打开后执行”是决定性的
|
6
AoEiuV020CN 2023-04-20 11:24:53 +08:00
@julyclyde #5 假设一个场景,ls 是一个一个遍历目录下所有文件,一个一个打印输出,同时没有缓冲,
显然在遍历到 foo.txt 之前的的文件信息会被输出到 foo.txt 中,在读取 foo.txt 时就会有一定的内容在里面, |
7
julyclyde 2023-04-20 11:26:56 +08:00
@AoEiuV020CN
我不知道 close 之前,内容是否真的完成写入了 |
8
AoEiuV020CN 2023-04-20 11:39:33 +08:00
@julyclyde #7 这就是取决于缓冲了,没有输出缓冲的话 write 完成内容就真的完成写入了,
有缓冲的话就是缓冲满了或者 close 或者 flush 才会真的写入, |
9
aloxaf 2023-04-20 11:47:33 +08:00
你要知道,重定向是流式的,所以只能先打开文件,再执行命令。
不需要流式的场景,倒是可以最后再写入文件,不过这样就得先缓存所有输出,比如 sponge 命令 cat a.txt | sponge -a a.txt |
10
flush9f 2023-04-20 14:00:31 +08:00
其实这个和重定向的缓存没有半毛钱关系,因为 ls 没有直接调用 getdents ,而是调用的 opendir ,然而 opendir 会提前分配内存缓存文件夹里所有的项,所以输出的文件大小自然是 0 ,因为 opendir 的时候它还啥都没写。想要验证很简单
jot 10000 | xargs touch ; ls -l . . > foo.txt |
11
msg7086 2023-04-20 14:10:05 +08:00
1. bash 打开了 foo.txt 并获得了文件描述符
2. bash 启动了 ls ,启动的时候把文件描述符塞进了 ls 的嘴里 3. ls 列表,看到了 foo.txt 4. ls 退出,foo.txt 关闭并刷出内容 |
12
MrKrabs 2023-04-20 14:18:03 +08:00
posix_spawn_file_actions_addopen
|
13
kaedeair 2023-04-20 15:30:11 +08:00
这和 ps|grep xx 总是能看到一条结果的原理一样
|
14
momocraft 2023-04-20 15:44:11 +08:00
猜测这样写 shell 比较省事
为了不覆盖可能需要新开个临时文件, 启动程序, 等程序进程结束再 move 覆盖 |
15
CodeCodeStudy OP @kaedeair #13 ps | grep xx 是 ps 先执行,所以 grep 至少看到一条结果是正常的,但是 ls -l > foo.txt 却是先执行了重定向生成空文件,然后再执行 ls -l ,然后将标准输出重定向到文件里,我不明白的是为什么先生成空文件
|
16
tomychen 2023-04-20 18:12:55 +08:00
cmd > filename
会先创建文件. 然后再将标准输出写到文件 [root@localhost shm]# ps -ef | /usr/bin/grep aabbaa root 4233 4148 0 06:10 pts/5 00:00:00 /usr/bin/grep aabbaa 如果先执行了 ps 怎么会有 grep aabbaa? |
17
Alias4ck 2023-04-20 18:35:39 +08:00
建议了解一下重定向符号的 实现 #11 #2 都描述的很明确了 实在不行可以去做个操作系统实验
https://pdos.csail.mit.edu/6.828/2019/labs/sh.html 别人的实现 https://github.com/zhayujie/xv6-riscv-fall19/blob/master/lab-02-shell/nsh.c |
18
asilin 2023-04-20 18:39:59 +08:00 via Android
@CodeCodeStudy 是 grep 先执行哦,是不是有些反直觉,哈哈
|
19
CapNemo 2023-04-20 18:49:21 +08:00
创建文件
打开文件 创建子进程&绑定打开的文件描述符到子进程的输出 ls 指令开始执行 -> 文件是空的 ls 命令输出空结果到输出缓冲区 ls 命令退出 输出缓冲区被同步,ls 命令的输出被写入到文件 -> 文件现在不为空 |
20
CodeCodeStudy OP |