Linux 重定向生成文件的执行顺序问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
CodeCodeStudy
V2EX    Linux

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

  •  
  •   CodeCodeStudy 2023-04-20 09:48:32 +08:00 2843 次点击
    这是一个创建于 970 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ls -l > foo.txt

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

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

    请问一下这是什么原理?

    20 条回复    2023-04-21 09:11:51 +08:00
    zhlxsh
        1
    zhlxsh  
       2023-04-20 09:53:55 +08:00 via iPhone
    这不就是从左到右吗?先执行 ls ,得到的结果是 foo 大小 0 。然后将结果写到 foo
    ysc3839
        2
    ysc3839  
       2023-04-20 10:04:40 +08:00 via Android
    因为“重定向 stdout 到文件”这个操作就是把新进程的“file descriptor 1”设置成目标文件,设置成目标文件之前要先打开目标文件,打开目标文件前要先创建目标文件,所以就先有了目标文件。
    AoEiuV020CN
        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 早已经读取完文件信息了,
    asilin
        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 最好看官方文档。
    julyclyde
        5
    julyclyde  
       2023-04-20 11:13:54 +08:00
    @AoEiuV020CN 跟输出缓冲没啥关系。“先打开后执行”是决定性的
    AoEiuV020CN
        6
    AoEiuV020CN  
       2023-04-20 11:24:53 +08:00
    @julyclyde #5 假设一个场景,ls 是一个一个遍历目录下所有文件,一个一个打印输出,同时没有缓冲,
    显然在遍历到 foo.txt 之前的的文件信息会被输出到 foo.txt 中,在读取 foo.txt 时就会有一定的内容在里面,
    julyclyde
        7
    julyclyde  
       2023-04-20 11:26:56 +08:00
    @AoEiuV020CN
    我不知道 close 之前,内容是否真的完成写入了
    AoEiuV020CN
        8
    AoEiuV020CN  
       2023-04-20 11:39:33 +08:00
    @julyclyde #7 这就是取决于缓冲了,没有输出缓冲的话 write 完成内容就真的完成写入了,
    有缓冲的话就是缓冲满了或者 close 或者 flush 才会真的写入,
    aloxaf
        9
    aloxaf  
       2023-04-20 11:47:33 +08:00
    你要知道,重定向是流式的,所以只能先打开文件,再执行命令。
    不需要流式的场景,倒是可以最后再写入文件,不过这样就得先缓存所有输出,比如 sponge 命令 cat a.txt | sponge -a a.txt
    flush9f
        10
    flush9f  
       2023-04-20 14:00:31 +08:00
    其实这个和重定向的缓存没有半毛钱关系,因为 ls 没有直接调用 getdents ,而是调用的 opendir ,然而 opendir 会提前分配内存缓存文件夹里所有的项,所以输出的文件大小自然是 0 ,因为 opendir 的时候它还啥都没写。想要验证很简单
    jot 10000 | xargs touch ; ls -l . . > foo.txt
    msg7086
        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 关闭并刷出内容
    MrKrabs
        12
    MrKrabs  
       2023-04-20 14:18:03 +08:00
    posix_spawn_file_actions_addopen
    kaedeair
        13
    kaedeair  
       2023-04-20 15:30:11 +08:00
    这和 ps|grep xx 总是能看到一条结果的原理一样
    momocraft
        14
    momocraft  
       2023-04-20 15:44:11 +08:00
    猜测这样写 shell 比较省事

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

    打开文件

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

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

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

    ls 命令退出

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

    @asilin #18

    哦,对,不然 grep 出来的结果也不会有 ps 了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     923 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 22:27 PVG 06:27 LAX 14:27 JFK 17:27
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86