【问题标题】:Why does `>` redirect not capture substituted processes' stdout?为什么`>`重定向不捕获替代进程的标准输出?
【发布时间】:2018-07-22 15:28:02
【问题描述】:

answer 关于管道和重定向的问题中,罗伯特提到管道还捕获管道中替代进程的标准输出,而重定向没有。为什么会这样?究竟发生了什么,导致了这种行为:

bash-4.1$ echo -e '1\n2' | tee >(head -n1) >redirect
1
bash-4.1$ cat redirect
1
2
bash-4.1$ echo -e '1\n2' | tee >(head -n1) | cat >pipe
bash-4.1$ cat pipe
1
2
1

我原以为这两种形式会产生相同的结果——后者。

阅读answer 到另一个问题,在命令中重新排序重定向可能会产生所需的结果似乎是合理的,但无论顺序如何,结果总是相同的:

bash-4.1$ echo -e '1\n2' | tee >redirect >(head -n1)
1
bash-4.1$ cat redirect
1
2
bash-4.1$ echo -e '1\n2' | >redirect tee >(head -n1)
1
bash-4.1$ cat redirect
1
2

为什么 stdout 重定向只影响tee,但管道也会捕获替换的进程head?只是“按设计”?


只是与上述问题相关的一个想法:我认为重定向到一个文件管道输出永远不会有意义,但它确实对进程替换有意义:

bash-4.1$ echo -e '1\n2\n3' | tee >(head -n1) >(tail -n1) >tee_out | cat >subst_out
bash-4.1$ cat tee_out
1
2
3
bash-4.1$ cat subst_out
1
3

【问题讨论】:

    标签: bash output-redirect process-substitution


    【解决方案1】:

    运行head 的shell 由运行tee 的同一shell 生成,这意味着teehead 都继承了相同的标准输出文件描述符,该文件描述符连接到管道cat。这意味着teehead 的输出都通过管道传输到cat,从而导致您看到的行为。

    【讨论】:

      【解决方案2】:

      对于

      echo -e '1\n2' | tee >(head -n1) > redirect
      

      ,在|之后,只有tee的stdout被重定向到文件,head仍然输出到tty。要重定向teehead 的标准输出,您可以编写

      echo -e '1\n2' | { tee >(head -n1); } > redirect
      

      { echo -e '1\n2' | tee >(head -n1); } > redirect
      

      对于

      echo -e '1\n2' | tee >(head -n1) | cat > pipe
      

      tee >(head -n1) 作为一个整体,他们的标准输出通过管道传输到cat。逻辑上与echo -e '1\n2' | { tee >(head -n1); } > redirect相同。

      【讨论】:

        【解决方案3】:

        TL;DR:当执行管道的一部分时,shell 执行标准输入/标准输出的管道重定向first>/< 重定向last时间>。命令替换发生在这两者之间,因此 stdin/stdout 的管道重定向被继承,而 >/< 重定向不是。这是一个设计决定。


        公平地说,我接受了 chepner 的回答,因为他是第一个而且他是正确的。但是,我决定通过阅读 bash 的资料来添加我自己的答案来记录我理解这个问题的过程,因为 chepner 的答案没有解释为什么 >/< 重定向没有被继承。


        当 shell 遇到复杂的管道时,了解所涉及的步骤(大大简化)会很有帮助。我已将我原来的问题简化为这个例子:

        $ echo x >(echo y) >file
        y
        $ cat file
        x /dev/fd/63
        
        $ echo x >(echo y) | cat >file
        $ cat file
        x /dev/fd/63
        y
        

        仅重定向

        当shell遇到echo x >(echo y) >file时,它首先fork一次执行复杂的命令(这在某些情况下可以避免,比如builtins),然后是fork的shell:

        1. 创建pipe(用于进程替换)
        2. 第二次回声分叉
          1. fork:将其stdin 连接到pipe[1]
          2. fork:exec's echo y; exec'ed echo 继承:
            • 标准输入连接到管道[1]
            • 标准输出不变
        3. 打开file
        4. 将其stdout 连接到file
        5. exececho x /proc/<pid>/fd/<pipe id>exec'ed echo 继承:
          • 标准输入不变
          • 标准输出连接到file

        这里,第二个 echo 继承了分叉 shell 的 stdout,然后分叉 shell 将其 stdout 重定向到 file。在这种情况下,我认为这种操作顺序没有绝对必要性,但我认为这样更有意义。

        管道重定向

        当shell遇到echo x >(echo y) | cat >file时,它会检测到一个管道并开始处理它(不分叉):

        1. parent:创建一个pipe(对应完整命令中唯一实际的|
        2. 父级:pipe 左侧的分叉
          1. fork1:将其stdout 连接到pipe[0]
          2. fork1:创建pipe_subst(用于进程替换)
          3. fork1:第二个回声的叉子
            1. nested-fork:将其stdin 连接到pipe_subst[1]
            2. 嵌套分叉:exec's echo y; exec'ed echo 继承:
              • stdin 从内叉连接到pipe_subst[1]
              • stdout 从外叉连接到pipe[0]
          4. fork1: exec's echo x /proc/<pid>/fd/<pipe_subst id>; exec'ed echo 继承:
            • 标准输入不变
            • 标准输出连接到pipe[0]
        3. parent:pipe 右侧的分叉(同样,有时可以避免这种分叉)
          1. fork2:将其stdin 连接到pipe[1]
          2. fork2:打开file
          3. fork2:将其stdout 连接到file
          4. fork2: exec's cat; exec'ed cat 继承:
            • 标准输入连接到pipe[1]
            • 标准输出连接到file

        在这里,管道优先,即在执行管道元素时执行任何其他操作之前执行由于管道而导致的 stdin/stdout 重定向。因此echo 都继承了stdout 重定向到cat


        所有这些实际上都是在进程替换后处理>file 重定向的设计结果。如果在此之前处理了 >file 重定向(就像管道重定向一样),那么 >file 也将被替代进程继承。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-06-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多