V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
CodeCodeStudy
V2EX  ›  Linux

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

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

    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   ·   实用小工具   ·   1482 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 17:24 · PVG 01:24 · LAX 09:24 · JFK 12:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.