私は機能的に同じと思われる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を正しく理解した場合、彼は標準出力に出力しないとサブシェルが>(sed 's/^/stdout: /')
実行されない( echo foo >&2 )
ことを提案しました。しかし、これは次のことを意味します。
$ ( echo foo >&2 ) 2> >(sed 's/^/stderr: /') > >(echo baz)
baz
stderr: foo
表示しないでくださいbaz
。
stdout:
編集2:おそらく興味深いことに、sedはパイピング中に空の入力に対して出力しません。
$ 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
それを標準出力に書き込み、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
、stderrを取得することは出力を生成しますが、stderr(何もなし)を取得する他は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
つまり、sed
2つのコマンドのうち2番目のコマンドは何も読みません。特に、最初のコマンドと同様に、最初のコマンドの出力を読み取ることはありません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
答え2
>
シェルのリダイレクト順序のために動作し、( 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
これら2つのコマンドは同じではありません。
コマンド(1):
( one ) 2> >(two) | three
(std)出力をtwo
次に送信しますthree
(stderrの詳細については、以下を参照)。
コマンド(2):
( one ) 2> >(two) > >(three)
(std)出力を(stderr)one
に送信します。three
two
胸の痛みの詳細は次のとおりです。
コマンド1:( one ) 2> >(two) | three
- コマンド
three
start(sed 's/^/stdout: /'
) は標準入力の入力を待ちます。 2
(stderr
)stdin
は〜にリダイレクトされますtwo
。one
内部リダイレクト(echo foo >&2
)が発生しました。- コマンドが実行されます
foo
(stderr
標準出力には送信されません)。 - 単語は
foo
(of)から(of)にリダイレクトされます。stderr
one
stdin
two
- 両方のコマンドを実行します(
sed 's/^/stderr: /'
) two
Modified()出力がstderr: foo
stdoutに送信されますtwo
。- 現在の標準出力は
two
パイプを介して到着しますthree
。 three
入力コマンドが最初から出力を受け取るのを待ちます。- 出力が修正され、リーダーが追加されます
stdout:
。 - 最後の文字列は
stdout: stderr: foo
ttyに移動します。
コマンド 2: (1) 2 >> (2) >> (3)
- 最後のリダイレクト(
>
)が最初に構築されます。 - コマンドが
three
入力待ちを開始します。stdin
ただ(>()
)。 - このコマンド
three
はただ標準出力one
。 - コマンドが
two
入力待ちを開始します。 - このコマンドは
two
(ただ)標準エラーone
。 - 内部リダイレクトはstdoutを
one
ここにリンクしますstderr
。 - コマンドは(of)として
one
送信され、実行されます。foo
stderr
one
foo
行くtwo
two
受信した文字列を変更してstderr: foo
次に送信します。端末。three
stdoutから空の入力を受け取りますone
。three
印刷何もない(処理する行がないからです。)
このコマンドについて学びます。
printf '' | sed 's/^/initial: /'
出力がありません(そして動作させる方法もありません)。
変更の順序: コマンド 3: (1) >>(3) 2>>(2)
two
練習としてthree
詳細出力の代わりにに行きなさい。tty
答え4
私は@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: /')
。
別のプロセス交換内にプロセス交換を含める方法を説明するために、これを追加することを検討しました。読みやすくするためにいいえより高いレベルの埋め込みにプッシュします。しかし、何らかの理由で避ける必要がある場合管路そして関連シェル、これは良い解決策です。