Linux 重定向生成文件的执行顺序问题

2023-04-20 09:48:32 +08:00
 CodeCodeStudy

ls -l > foo.txt

再通过 cat foo.txt 查看文件内容可以看到 foo.txt 的文件大小是 0

也就是说,先生成了文件,然后再执行 ls 命令,然后再将内容输出到文件里

请问一下这是什么原理?

1702 次点击
所在节点    Linux
20 条回复
zhlxsh
2023-04-20 09:53:55 +08:00
这不就是从左到右吗?先执行 ls ,得到的结果是 foo 大小 0 。然后将结果写到 foo
ysc3839
2023-04-20 10:04:40 +08:00
因为“重定向 stdout 到文件”这个操作就是把新进程的“file descriptor 1”设置成目标文件,设置成目标文件之前要先打开目标文件,打开目标文件前要先创建目标文件,所以就先有了目标文件。
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 早已经读取完文件信息了,
asilin
2023-04-20 10:16:48 +08:00
所以这就是为什么不能使用“grep 有效信息 a.txt > a.txt”的方式进行原地文本内容替换的原因了。

因为 bash 首先会将 a.txt 文件清空,然后再使用 grep 查询,那结果当然是 a.txt 文件被毁掉了。

很多刚入门 Linux 的初学者,会经常性或者直觉性的犯一些这样的错误。

类似的还有"ls a b c | grep" 这种命令,注意“|” 只接收标准输出,而不接收标准错误,如果要将管道前面的所有输出(包括错误信息)给到 grep 命令,那就得使用"|&"来代替"|"才能实现。

诸如此类的知识,在 bash 的 man 文档和 info 文档中具有体现和介绍,所以推荐学习 bash 和 shell 最好看官方文档。
julyclyde
2023-04-20 11:13:54 +08:00
@AoEiuV020CN 跟输出缓冲没啥关系。“先打开后执行”是决定性的
AoEiuV020CN
2023-04-20 11:24:53 +08:00
@julyclyde #5 假设一个场景,ls 是一个一个遍历目录下所有文件,一个一个打印输出,同时没有缓冲,
显然在遍历到 foo.txt 之前的的文件信息会被输出到 foo.txt 中,在读取 foo.txt 时就会有一定的内容在里面,
julyclyde
2023-04-20 11:26:56 +08:00
@AoEiuV020CN
我不知道 close 之前,内容是否真的完成写入了
AoEiuV020CN
2023-04-20 11:39:33 +08:00
@julyclyde #7 这就是取决于缓冲了,没有输出缓冲的话 write 完成内容就真的完成写入了,
有缓冲的话就是缓冲满了或者 close 或者 flush 才会真的写入,
aloxaf
2023-04-20 11:47:33 +08:00
你要知道,重定向是流式的,所以只能先打开文件,再执行命令。
不需要流式的场景,倒是可以最后再写入文件,不过这样就得先缓存所有输出,比如 sponge 命令 cat a.txt | sponge -a a.txt
flush9f
2023-04-20 14:00:31 +08:00
其实这个和重定向的缓存没有半毛钱关系,因为 ls 没有直接调用 getdents ,而是调用的 opendir ,然而 opendir 会提前分配内存缓存文件夹里所有的项,所以输出的文件大小自然是 0 ,因为 opendir 的时候它还啥都没写。想要验证很简单
jot 10000 | xargs touch ; ls -l . . > foo.txt
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 关闭并刷出内容
MrKrabs
2023-04-20 14:18:03 +08:00
posix_spawn_file_actions_addopen
kaedeair
2023-04-20 15:30:11 +08:00
这和 ps|grep xx 总是能看到一条结果的原理一样
momocraft
2023-04-20 15:44:11 +08:00
猜测这样写 shell 比较省事

为了不覆盖可能需要新开个临时文件, 启动程序, 等程序进程结束再 move 覆盖
CodeCodeStudy
2023-04-20 16:20:55 +08:00
@kaedeair #13 ps | grep xx 是 ps 先执行,所以 grep 至少看到一条结果是正常的,但是 ls -l > foo.txt 却是先执行了重定向生成空文件,然后再执行 ls -l ,然后将标准输出重定向到文件里,我不明白的是为什么先生成空文件
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?
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
asilin
2023-04-20 18:39:59 +08:00
@CodeCodeStudy 是 grep 先执行哦,是不是有些反直觉,哈哈
CapNemo
2023-04-20 18:49:21 +08:00
创建文件

打开文件

创建子进程&绑定打开的文件描述符到子进程的输出

ls 指令开始执行 -> 文件是空的

ls 命令输出空结果到输出缓冲区

ls 命令退出

输出缓冲区被同步,ls 命令的输出被写入到文件 -> 文件现在不为空
CodeCodeStudy
2023-04-21 09:11:51 +08:00
@tomychen #16

@asilin #18

哦,对,不然 grep 出来的结果也不会有 ps 了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/933916

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX