我试图理解是什么导致了这两个我认为功能相同的结构的差异:
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /') | sed 's/^/stdout: /'
stdout: stderr: foo
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /') > >(sed 's/^/stdout: /')
stderr: foo
编辑:如果我理解 user1133275 正确,他建议>(sed 's/^/stdout: /')
除非子 shell( echo foo >&2 )
输出到标准输出,否则不会运行。然而,这意味着:
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /') > >(echo baz)
baz
stderr: foo
不应显示baz
。
编辑2:也许也有趣的是,sed 不会stdout:
在空输入上输出,即使在管道传输时也是如此:
$ sed 's/^/stdout: /' < /dev/null
$ printf "" | sed 's/^/stdout: /'
$
答案1
你的第一个命令,
( echo foo >&2 ) 2> >(sed 's/^/stderr: /') | sed 's/^/stdout: /'
以简化形式(使用临时文件来保存 生成的数据echo
):
{ echo foo 2>file >&2; sed 's/^/stderr: /' file; } | sed 's/^/stdout: /'
即,第一个sed
从标准错误中读取生成的内容echo
并将其写入标准输出,第二个sed
读取并修改它。
你的第二个命令,
( echo foo >&2 ) 2> >(sed 's/^/stderr: /') > >(sed 's/^/stdout: /')
以简化形式,
echo foo 2>file >&2; sed 's/^/stderr: /' file; sed 's/^/stdout: /' </dev/null
在这里,sed
获取标准错误输出的 会产生输出,而另一个sed
获取标准输出的输出(什么也没有)不会产生任何输出(因为它没有获取任何输入,并且没有插入或附加任何数据) 。
另一种表述方式:
第一个命令:
( echo foo >&2 ) 2>file
sed 's/^/stderr: /' file | sed 's/^/stdout: /'
第二条命令:
( echo foo >&2 ) 2>file >otherfile
sed 's/^/stderr: /' file
sed 's/^/stdout: /' otherfile
简而言之,sed
第二个命令中的第二个永远不会读取任何内容。特别是,它不会像第一个命令那样读取第一个命令的输出sed
。
使用极其简化的符号,第一个命令类似于
utility-writing-to-stderr 2> >(something1) | something2
其中something1
写入标准输出,由 读取something2
。
第二个命令使用相同的符号,
utility-writing-to-stderr 2> >(something1) >(something2)
iesomething1
甚至something2
彼此没有连接,并且something2
无法以任何方式读取something1
正在生成的内容。此外,由于utility-writing-to-stderr
不会在其标准输出流上产生任何内容,something2
因此无法从其标准输入中读取任何内容。
答案2
>
由于 shell 中的重定向顺序,正在操作( echo foo >&2 )
并 |
正在操作。>(sed 's/^/stderr: /')
例如
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /' > >(sed 's/^/stdout: /') )
stdout: stderr: foo
或者
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /') | cat > >(sed 's/^/stdout: /')
stdout: stderr: foo
与明确的例子问题中的顺序
$ ( ( echo foo >&2 ) > >(sed 's/^/stdout: /') ) 2> >(sed 's/^/stderr: /')
stderr: foo
或者
$ ( ( echo foo >&2 ) | sed 's/^/stdout: /' ) 2> >(sed 's/^/stderr: /')
stderr: foo
答案3
这两个命令并不等效。
命令(1):
( one ) 2> >(two) | three
将 (std) 输出发送two
至three
(有关 stderr 的更多信息见下文)。
命令(2):
( one ) 2> >(two) > >(three)
将(std)输出发送one
到three
(stderr 发送到two
)。
令人心痛的细节是:
命令1:( one ) 2> >(two) | three
- 命令
three
启动 (sed 's/^/stdout: /'
) 等待标准输入上的输入。 2
(stderr
)重定向至stdin
oftwo
。one
发生内部重定向(echo foo >&2
)。- 该命令被执行发送
foo
到stderr
(没有发送到标准输出)。 - 该单词从 ( of )
foo
重定向到 ( of )。stderr
one
stdin
two
- 执行两个中的命令 (
sed 's/^/stderr: /'
) two
现在修改的( )输出stderr: foo
被发送到 的 stdouttwo
。- 当前的标准输出
two
通过管道到达three
。 three
从一开始就等待输入的命令获取输出。- 输出被修改,添加了一个前导
stdout:
. - 最后的字符串
stdout: stderr: foo
转到 tty。
命令2:(一)2>>(二)>>(三)
- 最后一个重定向 (
>
) 首先构建。 - 命令
three
开始等待输入stdin
仅有的(>()
)。 - 该命令
three
得到仅有的的标准输出one
。 - 该命令
two
开始等待输入。 - 该命令
two
得到(仅有的) 的标准错误one
。 - 一个的内部重定向将 的 stdout 连接
one
到其stderr
. - 该命令
one
被执行发送foo
到stderr
(ofone
)。 foo
去two
two
更改接收到的字符串stderr: foo
并将其发送到终端。three
从 的 stdout 接收一个空输入one
。three
印刷没有什么(因为没有要处理的行)。
了解这个命令:
printf '' | sed 's/^/initial: /'
没有输出(并且没有办法让它工作)。
更改顺序: 命令 3:( 一 ) >>(三) 2>>(二)
two
将go to three
(而不是)详细信息的输出tty
留作练习。
答案4
我赞成@Kusalananda 非常明确的答案,但我冒险(迟来的)将其添加到他的答案中,作为工作中流程替代的一个小例子:
你的第二个命题可以通过一个小的印刷更改(以及其错误逻辑的相对较大的更改)来实现:
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /') > >(sed 's/^/stdout: /')
stderr: foo # for the well explained reason above
但:
v vv
$ ( (echo foo >&2) 2> >(sed 's/^/stderr: /') )> >(sed 's/^/stdout: /')
stdout: stderr: foo
因为( )>
如图所示添加(或>( )
,两者都可以正常工作)实际上允许您通过进程替换创建一个文件,其输出可以重定向为 的输入>(sed 's/^/stdout: /')
。
我考虑添加这个来说明如何将进程替换嵌入到另一个进程替换中。为了可读性我会不是将其推向更高层次的嵌入。但如果出于任何原因您需要避免管道以及相关的壳,这是一个很好的解决方案。