为什么`>`重定向不捕获替代进程的标准输出?
Why does `>` redirect not capture substituted processes' stdout?
在 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
我原以为这两种形式会产生相同的结果——后者。
读一个 到另一个问题,重新排序命令中的重定向可能会产生所需的结果似乎是合理的,但无论顺序如何,结果总是相同的:
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
?只需 "By design"?
只是与上述问题相关的一个想法:我认为重定向到文件 和 管道输出永远没有意义,但它对进程替换确实有意义:
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
运行 head
的 shell 是由运行 tee
的同一个 shell 生成的,这意味着 tee
和 head
两者为标准输出继承相同的文件描述符,该文件描述符连接到 cat
的管道。这意味着 tee
和 head
都将它们的输出通过管道传输到 cat
,从而导致您看到的行为。
为了
echo -e '1\n2' | tee >(head -n1) > redirect
,在 |
之后,只有 tee
的标准输出被重定向到文件,而 head
仍然输出到 tty。要重定向 tee
和 head
的标准输出,您可以编写
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
.
相同
TL;DR:当执行部分管道时,shell 首先执行 stdin/stdout 的管道重定向 和 >/<
重定向 last。命令替换发生在这两者之间,因此 stdin/stdout 的管道重定向被继承,而 >/<
重定向不是。这是一个设计决定。
公平地说,我接受了 chepner 的回答,因为他是第一个而且他是正确的。但是,我决定通过阅读 bash 的来源添加我自己的答案来记录我理解这个问题的过程,因为 chepner 的答案没有解释为什么 >/<
重定向 isn' t继承。
当 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 :
- 创建一个
pipe
(用于进程替换)
- 分叉第二次回声
- 分叉:将其
stdin
连接到 pipe[1]
- 分叉:
exec
的 echo y
; exec
'ed echo
继承:
- stdin 连接到管道[1]
- 未更改标准输出
- 打开
file
- 将其
stdout
连接到 file
exec
的echo x /proc/<pid>/fd/<pipe id>
; exec
'ed echo
继承:
- 标准输入不变
- stdout 连接到
file
这里,第二个echo继承了分叉shell的stdout
,然后分叉shell将其stdout
重定向到file
。在这种情况下,我认为这种行动顺序没有绝对必要,但我认为这样更有意义。
管道重定向
当 shell 遇到 echo x >(echo y) | cat >file
时,它检测到管道并开始处理它(不分叉):
- parent:创建一个
pipe
(对应完整命令中唯一实际的|
)
- 父级:
pipe
左侧的分叉
- fork1:将其
stdout
连接到 pipe[0]
- fork1:创建一个
pipe_subst
(用于进程替换)
- fork1:分叉第二次回显
- nested-fork:将其
stdin
连接到 pipe_subst[1]
- 嵌套分叉:
exec
的 echo y
; exec
'ed echo
继承:
- stdin 从内叉
连接到 pipe_subst[1]
- stdout 从外叉
连接到 pipe[0]
- fork1:
exec
的 echo x /proc/<pid>/fd/<pipe_subst id>
; exec
'ed echo
继承:
- 标准输入不变
- stdout 连接到
pipe[0]
- 父级:
pipe
右侧的分叉(同样,有时可以避免这种分叉)
- fork2:将其
stdin
连接到 pipe[1]
- fork2:打开
file
- fork2:将其
stdout
连接到 file
- fork2:
exec
的 cat
; exec
'ed cat
继承:
- stdin 连接到
pipe[1]
- stdout 连接到
file
此处,管道优先,即由于管道而导致的 stdin/stdout 重定向在执行管道元素的任何其他操作发生之前执行。因此,echo
都继承了重定向到 cat
.
的 stdout
所有这些实际上是 >file
在进程替换后处理重定向的设计结果。如果在此之前处理了 >file
重定向(就像管道重定向一样),那么 >file
也会被替换的进程继承。
在 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
我原以为这两种形式会产生相同的结果——后者。
读一个
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
?只需 "By design"?
只是与上述问题相关的一个想法:我认为重定向到文件 和 管道输出永远没有意义,但它对进程替换确实有意义:
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
运行 head
的 shell 是由运行 tee
的同一个 shell 生成的,这意味着 tee
和 head
两者为标准输出继承相同的文件描述符,该文件描述符连接到 cat
的管道。这意味着 tee
和 head
都将它们的输出通过管道传输到 cat
,从而导致您看到的行为。
为了
echo -e '1\n2' | tee >(head -n1) > redirect
,在 |
之后,只有 tee
的标准输出被重定向到文件,而 head
仍然输出到 tty。要重定向 tee
和 head
的标准输出,您可以编写
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
.
TL;DR:当执行部分管道时,shell 首先执行 stdin/stdout 的管道重定向 和 >/<
重定向 last。命令替换发生在这两者之间,因此 stdin/stdout 的管道重定向被继承,而 >/<
重定向不是。这是一个设计决定。
公平地说,我接受了 chepner 的回答,因为他是第一个而且他是正确的。但是,我决定通过阅读 bash 的来源添加我自己的答案来记录我理解这个问题的过程,因为 chepner 的答案没有解释为什么 >/<
重定向 isn' t继承。
当 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 :
- 创建一个
pipe
(用于进程替换) - 分叉第二次回声
- 分叉:将其
stdin
连接到pipe[1]
- 分叉:
exec
的echo y
;exec
'edecho
继承:- stdin 连接到管道[1]
- 未更改标准输出
- 分叉:将其
- 打开
file
- 将其
stdout
连接到file
exec
的echo x /proc/<pid>/fd/<pipe id>
;exec
'edecho
继承:- 标准输入不变
- stdout 连接到
file
这里,第二个echo继承了分叉shell的stdout
,然后分叉shell将其stdout
重定向到file
。在这种情况下,我认为这种行动顺序没有绝对必要,但我认为这样更有意义。
管道重定向
当 shell 遇到 echo x >(echo y) | cat >file
时,它检测到管道并开始处理它(不分叉):
- parent:创建一个
pipe
(对应完整命令中唯一实际的|
) - 父级:
pipe
左侧的分叉- fork1:将其
stdout
连接到pipe[0]
- fork1:创建一个
pipe_subst
(用于进程替换) - fork1:分叉第二次回显
- nested-fork:将其
stdin
连接到pipe_subst[1]
- 嵌套分叉:
exec
的echo y
;exec
'edecho
继承:- stdin 从内叉 连接到
- stdout 从外叉 连接到
pipe_subst[1]
pipe[0]
- nested-fork:将其
- fork1:
exec
的echo x /proc/<pid>/fd/<pipe_subst id>
;exec
'edecho
继承:- 标准输入不变
- stdout 连接到
pipe[0]
- fork1:将其
- 父级:
pipe
右侧的分叉(同样,有时可以避免这种分叉)- fork2:将其
stdin
连接到pipe[1]
- fork2:打开
file
- fork2:将其
stdout
连接到file
- fork2:
exec
的cat
;exec
'edcat
继承:- stdin 连接到
pipe[1]
- stdout 连接到
file
- stdin 连接到
- fork2:将其
此处,管道优先,即由于管道而导致的 stdin/stdout 重定向在执行管道元素的任何其他操作发生之前执行。因此,echo
都继承了重定向到 cat
.
stdout
所有这些实际上是 >file
在进程替换后处理重定向的设计结果。如果在此之前处理了 >file
重定向(就像管道重定向一样),那么 >file
也会被替换的进程继承。