私は機能的に同等であると私が思ったこれらの2つの構成の違いの原因を理解しようとしています:
_$ ( 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の権利を理解した場合、サブシェル_( echo foo >&2 )
_がstdoutに出力されない限り、>(sed 's/^/stdout: /')
は実行されないことを彼は提案します。ただし、次のことを意味します。
_$ ( 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: /'
$
_
最初のコマンド、
( 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
から標準エラーで生成されたものを読み取り、標準出力に書き込み、2番目のsed
はそれを読み取って変更します。
2番目のコマンド、
( 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: /'
2番目のコマンド:
( echo foo >&2 ) 2>file >otherfile
sed 's/^/stderr: /' file
sed 's/^/stdout: /' otherfile
つまり、2番目のコマンドの2番目のsed
は何も読み取りません。特に、最初のコマンドのように最初のsed
からの出力を読み取りません。
非常に単純化されたシンボルを使用して、最初のコマンドは次のようなものです
utility-writing-to-stderr 2> >(something1) | something2
ここで、something1
は、something2
によって読み取られる標準出力に書き込みます。
同じ表記を使用する2番目のコマンド、
utility-writing-to-stderr 2> >(something1) >(something2)
つまり、something1
とsomething2
は互いに接続されておらず、something2
はsomething1
が生成しているものを決して読み取ることができません。さらに、utility-writing-to-stderr
は標準出力ストリームで何も生成しないため、something2
は標準入力から読み取るものがありません。
シェルでのリダイレクトの順序のため、_>
_は_( 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
_
2つのコマンドは同等ではありません。
コマンド(1):
_( one ) 2> >(two) | three
_
two
の(std)出力をthree
に送信します(stderrの詳細については以下を参照してください)。
コマンド(2):
_( one ) 2> >(two) > >(three)
_
one
の(std)出力をthree
に送信します(stderrはtwo
に移動します)。
痛みを伴う詳細は次のとおりです。
コマンド1:_( one ) 2> >(two) | three
_
three
が開始され(_sed 's/^/stdout: /'
_)、stdinへの入力を待機しています。2
_(stderr
)からstdin
のtwo
へのリダイレクトが行われます。one
の内部リダイレクトが行われます(_echo foo >&2
_)。foo
をstderr
に送信して実行されます(標準出力には何もありません)。foo
は(stderr
of one
)から(stdin
of two
)にリダイレクトされます。sed 's/^/stderr: /'
_)two
(_stderr: foo
_)の出力は、two
のstdoutに送信されます。two
の標準出力は、パイプを介してthree
に送られます。three
が出力を取得します。stdout:
_を追加して変更されます。stdout: stderr: foo
_はttyに送られます。コマンド2 :( 1)2 >>(2)>>(3)
>
_)が最初にビルドされます。three
は、stdin
のみ(>()
)で入力を待機して開始されます。three
はのみone
の標準出力を取得します。two
が入力待ちで開始されます。two
は、one
のstderrを取得します(only)。one
のstdoutをそのstderr
に接続します。one
が実行され、foo
が(stderr
の)one
に送信されます。foo
はtwo
に移動しますtwo
受信した文字列を_stderr: foo
_に変更し、それをttyに送信します。three
は、one
のstdoutから空の入力を受け取ります。three
prints nothing(処理する行がないため)。このコマンドを理解してください:
_printf '' | sed 's/^/initial: /'
_
出力がありません(そしてそれを機能させる方法はありません)。
順序の変更:コマンド3:(one)>>(three)2>>(two)
演習として残されたtwo
の出力をthree
(tty
ではない)の詳細に移動します。
私は@Kusalanandaの非常に明確な答えに賛成しましたが、仕事でのプロセス置換の小さな例として、これを(遅ればせながら)彼の答えに追加しようと思います。
2番目の命題は、小さな誤植の変更(およびその誤ったロジックの比較的大きな変更)で機能します。
_ $ ( 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: /')
にリダイレクトできます。
これを追加して、プロセス置換を別のプロセス置換に埋め込む方法を説明することを考えました。読みやすくするために、notより多くのレベルの埋め込みにプッシュします。しかし、何らかの理由でpipeおよび関連するShell、これは良い解決策です。