为什么`... |当 `... > >(sed 's/^/stdout: /')` 不打印时,sed 's/^/stdout: /'` 在空标准输入上打印?

为什么`... |当 `... > >(sed 's/^/stdout: /')` 不打印时,sed 's/^/stdout: /'` 在空标准输入上打印?

我试图理解是什么导致了这两个我认为功能相同的结构的差异:

$ ( 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) 输出发送twothree(有关 stderr 的更多信息见下文)。

命令(2):

( one ) 2> >(two) > >(three)

将(std)输出发送onethree(stderr 发送到two)。


令人心痛的细节是:

命令1:( one ) 2> >(two) | three

  • 命令three启动 ( sed 's/^/stdout: /') 等待标准输入上的输入。
  • 2( stderr)重定向至stdinof two
  • one发生内部重定向( echo foo >&2)。
  • 该命令被执行发送foostderr(没有发送到标准输出)。
  • 该单词从 ( of )foo重定向到 ( of )。stderronestdintwo
  • 执行两个中的命令 ( sed 's/^/stderr: /')
  • two现在修改的( )输出stderr: foo被发送到 的 stdout two
  • 当前的标准输出two通过管道到达three
  • three从一开始就等待输入的命令获取输出。
  • 输出被修改,添加了一个前导stdout:.
  • 最后的字符串stdout: stderr: foo转到 tty。

命令2:(一)2>>(二)>>(三)

  • 最后一个重定向 ( >) 首先构建。
  • 命令three开始等待输入stdin 仅有的( >())。
  • 该命令three得到仅有的的标准输出one
  • 该命令two开始等待输入。
  • 该命令two得到(仅有的) 的标准错误one
  • 一个的内部重定向将 的 stdout 连接one到其stderr.
  • 该命令one被执行发送foostderr(of one)。
  • footwo
  • 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: /')

我考虑添加这个来说明如何将进程替换嵌入到另一个进程替换中。为了可读性我会不是将其推向更高层次的嵌入。但如果出于任何原因您需要避免管道以及相关的,这是一个很好的解决方案。

相关内容