3

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

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

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

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

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
4

3 回答 3

3

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

于 2018-02-12T13:12:35.767 回答
1

为了

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

, 之后|, 只有tee的标准输出被重定向到文件并且head仍然输出到 tty。要同时重定向teeandhead的标准输出,您可以编写

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.

于 2018-02-12T15:35:27.080 回答
0

TL;DR:当执行管道的一部分时,shell首先执行 stdin/stdout的管道>/<重定向,最后执行重定向。命令替换发生在这两者之间,因此 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,它首先分叉一次以执行复杂的命令(这在某些情况下可以避免,例如内置命令),然后是分叉的 shell:

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

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

管道重定向

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

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

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


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

于 2018-02-13T14:45:49.670 回答