V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
geelaw
V2EX  ›  问与答

*nix 系的一个管道问题

  •  
  •   geelaw · 2017-06-01 23:26:18 +08:00 · 1971 次点击
    这是一个创建于 2739 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Codepad 链接: http://codepad.org/LFkfcDXW

    起因是我发现 Windows Command Prompt 的管道的一个有趣行为:

    考虑命令 a | b,如果 b 结束之后 a 不退出(比如 ayes,无脑循环输出 y 加换行),则 cmd 会 hang,可以用 Ctrl+C 打断

    这个问题自然是可以解决的,只要实现 yes 的时候判断一下输出有没有成功即可。

    自然我想知道 *nix 系里的管道是怎么一个情况。我的一个朋友告诉我执行 yes | 另一个程序 的时候,另一个程序结束的时候 yes 也会结束,不会 hang 住终端,也不会需要自己去 kill 那个 yes。我对这个机制感到很好奇……想问一下,如果有下面两段代码:

    /* program1.c */
    #include<cstdio>
    #include<unistd.h>
    int main(void)
    {
    sleep(5);
    fputs("I wrote to stderr!\n", stderr);
    return 0;
    }
    

    /* program2.c */
    int main(void){return 0;}
    

    如果我编译、执行 program1 | program2,那么有如下可能:

    1. 立刻结束,不会看到 I wrote to stderr!
    2. 立刻结束,但是五秒之后会突然蹦出来 I wrote to stderr!
    3. 不立刻结束,5 秒之后出现 I wrote to stderr! 并结束。

    如果是情况 1,显然是终端在管道末尾的程序停止的时候就 kill 了前面的所有进程;如果是情况 2,则是终端在管道末尾的程序挺值得时候就继续给出 prompt,但是前面的程序并没有停止;如果是情况 3,那就是终端要等到管道里面每个程序都停止的时候才继续给出 prompt。

    请问是哪一种呢?如果您有闲、使用 *nix 并想要帮助我,请帮我测试一下,谢谢 :-)

    9 条回复    2017-06-02 09:54:40 +08:00
    privil
        1
    privil  
       2017-06-01 23:38:28 +08:00   ❤️ 1
    ……我测试了,不会解释,五秒钟后会继续,不过我是随手用 shell 写的程序
    privil
        2
    privil  
       2017-06-01 23:38:55 +08:00
    @privil 情况 3
    pagxir
        3
    pagxir  
       2017-06-01 23:45:02 +08:00 via Android   ❤️ 1
    还是多系统的看书吧。yes 被自动结束是因为,它往一个没有读者的管道写数据而触发 broken pipe。broken pipe 的缺省动作是让程序终止
    pagxir
        4
    pagxir  
       2017-06-01 23:46:57 +08:00 via Android
    还是 windows 的 ctrl c 是假的,并不能真的触发 sigint。即 window 不支持作业控制。
    geelaw
        5
    geelaw  
    OP
       2017-06-02 00:01:25 +08:00
    @pagxir (自动解析为“多看系统的书”)很感谢您提供的原因。

    问这个问题是因为 Windows 上的行为并不是这样啊;而且我不是 *nix 用户,所以才来问。

    `WriteFile` 并不一定要让程序崩溃,只要输入的参数(在这个例子里面,传入的 handle 没有被关掉,直到这个 handle 被关掉才可以崩溃)还是有效的——它返回一个错误代码。而 `putchar` 的标准也只是说,如果成功则返回等于输入的值,并不一定要崩溃。

    Windows 的控制台 Ctrl+C 是可控制的,不是假的,根据这篇 MSDN 文档: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682541(v=vs.85).aspx

    一个常见的错误是把“系统”解读为“*nix ”。
    geelaw
        6
    geelaw  
    OP
       2017-06-02 00:10:06 +08:00
    --- CASE CLOSED ---

    *nix `yes` 会自己结束的原因是向无读者管道输出,而不是单纯“因为后一个程序已经结束”,也不是被终端、命令解释器杀死的。

    后一个程序结束之后前一个程序不一定要结束,并且终端、命令解释器会等到管道里面所有的程序都结束才会继续给出 prompt。

    --- CASE CLOSED ---

    我写的 PowerShell 二进制管道的实现里面,只要最后一个程序结束,对管道的消费就会结束,prompt 就可以出现了;我在思考是否应该等待管道里面所有的程序都结束再结束对管道的消费。
    billlee
        7
    billlee  
       2017-06-02 01:25:34 +08:00   ❤️ 2
    Shell 是不会杀你的进程的,终端更不会杀进程。
    POSIX 下,write 对端已经关闭的 pipe 会产生 SIGPIPE, 这个信号的默认行为是结束进程。你这个程序里面并没有向标准输出写数据,也就不会产生 SIGPIPE, 所以是情况 3.
    写 TCP 连接同样会有这样的问题,所以网络程序一般都是要忽略 SIGPIPE 的。
    xss
        8
    xss  
       2017-06-02 09:53:32 +08:00
    如果你执行的是
    ./a | ./b
    结果是等待 5 秒, 然后输出, 即情况 3
    如果你执行的是
    ./a 2>&1 | ./b
    结果是立刻结束, 无输出, 即情况 1

    你自己结合楼上的说的自己体会吧......
    xss
        9
    xss  
       2017-06-02 09:54:40 +08:00
    错了, 第二种情况是等待 5s, 无输出, 不是立刻结束....
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   978 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 20:19 · PVG 04:19 · LAX 12:19 · JFK 15:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.