web-dev-qa-db-ja.com

stderrの横にstdinを「貼り付け」できないのはなぜですか?

通常、pasteは、次のように隣接する列に2つの名前付き(または同等の)ファイルを出力します。

paste <(printf '%s\n' a b) <(seq 2)

出力:

a   1
b   2

ただし、2つのファイルが/dev/stdin/dev/stderrの場合、同じようには機能しないようです。

blackbboxプログラム標準出力標準エラーの2行。説明のために、これは次の関数でシミュレートできます。

bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }

ここで、 annotate-output 、(devscriptsパッケージのDebian/Ubuntu/etc。を実行します。 ))、それが機能することを示すために:

annotate-output bash -c 'bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }; bb'
22:06:17 I: Started bash -c bb() { seq 2 | tee >(sed s/^/e/ > /dev/stderr) ; }; bb
22:06:17 O: 1
22:06:17 E: e1
22:06:17 O: 2
22:06:17 E: e2
22:06:17 I: Finished with exitcode 0

だからそれは動作します。 bbpasteにフィードします。

bb | paste /dev/stdin /dev/stderr

出力:

1   e1
e2
^C

ハングします--^Cは、Control-Cを押して終了することを意味します。

|;に変更しても機能しません。

bb ; paste /dev/stdin /dev/stderr

出力:

1
2
e1
e2
^C

また、ハングします--^Cは、Control-Cを押して終了することを意味します。

必要な出力:

1    e1
2    e2

pasteを使用して実行できますか?そうでない場合は、なぜですか?

3
agc

/ dev/stderrをパイプラインとして使用できない理由

問題はpasteにあるのではなく、/dev/stdinにもありません。 /dev/stderrです。

すべてのコマンドは、1つのオープン入力記述子(0:標準入力)と2つの出力(1:標準出力および2:標準エラー)で作成されます。これらは通常、それぞれ/dev/stdin/dev/stdout、および/dev/stderrという名前でアクセスできますが、 / dev/stdin、/ dev/stdout、および/ dev/stderrの移植性の程度)を参照してください。 ?pasteを含む多くのコマンドも、ファイル名-をSTDINを意味すると解釈します。

bbを単独で実行すると、STDOUTとSTDERRの両方がコンソールになり、通常はコマンド出力が表示されます。行は(annotate-outputで示されているように)異なる記述子を通過しますが、最終的には同じ場所に配置されます。

|と2番目のコマンドを追加すると、パイプラインが作成されます...

bb | paste /dev/stdin /dev/stderr

|は、bbの出力をpasteの入力に接続するようにシェルに指示します。 pasteは最初に/dev/stdinからの読み取りを試みます。これは、(いくつかのシンボリックリンクを介して)独自の標準入力記述子(シェルが接続したばかり)に解決されるため、行1が通過します。

しかし、シェル/パイプラインはSTDERRには何もしません。 bbはまだそれ(e1e2など)をコンソールに送信します。その間、pasteは同じコンソールから読み込もうとしますが、ハングします(何かを入力するまで)。

あなたのリンク テキストエディタで/ dev/stdoutを読み取れないのはなぜですか?/dev/stderrにも同じ制限が適用されるため、ここでも関連性があります。

2番目のパイプラインの作り方

標準出力と標準エラーの両方を生成するコマンドがあり、これらの2行を隣り合わせにpasteしたいとします。つまり、各列に1つずつ、2つの同時パイプがあります。シェルパイプライン... | ...はそれらの1つを提供し、2つ目を自分で作成し、2>filenameを使用してSTDERRをそれにリダイレクトする必要があります。

mkfifo RHS
bb 2>RHS | paste /dev/stdin RHS

これがスクリプトで使用する場合は、一時ディレクトリにFIFOを作成し、使用後に削除することをお勧めします。

4
JigglyNaga

annotate-outputは何か特別なことをしているので(つまり、コマンドのstderrをfifoにリダイレクトする)、pasteが持っていること絶対に方法がない-単にpastenotコマンドを実行しているため、入力を取得し、入力または出力をリダイレクトする方法がありません。

ただし、annotate-outputが使用しているのとまったく同じトリックを使用するラッパーを作成できます。

pasteout(){
  f=$(mktemp -u) || return
  mkfifo -m 600 -- "$f" || return
  "$@" 2>"$f" | paste -- - "$f"
  rm -f -- "$f"
}
pasteout bb

ただし、デッドロックが発生しやすいことに注意してください。たとえば、bbがパイプに収まるよりも多くの標準出力とpasteによって最初に読み取られた余分な量を生成するが、エラー出力を生成しない場合、pasteはブロックされますfifoでの入力を待機し、パイプを空にしないbbはそのstdoutをフィードしているため、パイプへのbbのwrite()もハングします。

3
mosvy

分析する必要のあるライン全体には、いくつかの問題があります。つまり、次のとおりです。

seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | paste /dev/stdin /dev/stderr

stderr

まず、最後のコマンド。 stdoutのみがパイプを通過できます:

$ seq2 | paste -
1
2

$ seq2 | paste - -
1 2

stderrから読み取るものはありません:

$ seq 2 | paste - /dev/stderr 
1   ^C

ブロックするため、^Cする必要があります。stderrから読み取るものはありません。
stderrへの出力を作成しても、パイプを通過しません。

$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr
1   3
4

以前とまったく同じように、1が出力され、pasteブロックがstderrを待機します。
他の2つの数字は直接コンソールに送られ、(独立して)印刷されました。

パイプの最後のコマンドでstderrに入力を与えることができます。

$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr 2</dev/null
1
2
3
4

ちなみに、これは2>/dev/nullとまったく同じで、pasteコマンドで使用される2番目のファイル記述子のブロックを回避します。ただし、出力される値は、pasteからではなく、コンソールにリダイレクトされたseq 3 4から直接取得されます。これは同じことをします:

$ { seq 2; seq 3 4 >/dev/tty; } | paste - /dev/stderr 2</dev/null
1   
2   
3
4

そして、これはブロックしません:

$ seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | 
  paste /dev/stdin /dev/stderr 2</dev/null
1   
2   
e1
e2

注文

第二に、teeの出力は「順番に」ある必要はありません。 `tee`および` bash`プロセス置換順序

そして、実際には:プロセス置換の出力は「順序どおり」である必要はありません: プロセス置換の出力は順序が狂っています

$ echo one; echo two > >(cat); echo three;
one
three
two

実際、いくつかの例では、何度か試してみると、異なる注文を受け取る可能性があります。 プロセス置換によって同時に実行される独立したプロセスからの非決定論的出力

$ printf '%s\n' {0..1000} | tee >(head -n2) >(sort -grk1,1 | head -n3) >/dev/null
1000
999
998
0
1

したがって、いいえ、プロセス置換と貼り付けでは実行できませんでした。
実行に何らかの命令を与える必要があります:

$ seq 2 | { while read a; do printf "%s %s\n" "$a" "e$a" ; done; }
1 e1
2 e2

bb

したがって、bb関数には(基本的に)次のものが含まれます。

| tee >(sed 's/^/e/')

でテストできます:

$ printf '%s\n' {0..1000} | tee >(sort -grk1,1 | head -n3 >&2) | head -n 2
0
1
291
290
289

0、1、1000、999、998をこの順序で印刷する必要がありますが、多くの場合、印刷されません。
つまり、本質的に不安定です。

安定した実際のソリューション。

Bbの唯一の安全な解決策は、プロセス置換を回避することです。
そして、{…}がstdoutとstderrの両方をキャプチャすることを利用して、例:

$ bash -c '{ echo test-str >/dev/stderr; }' 2>/dev/null

出力がありません。確認のために2を削除してください。

これはbbで機能します:

$ bb() { seq 5 | tee /dev/stderr | sed 's/^/e/'; }

貼り付けにはFIFOを使用します。

$ mkfifo out2
$ bb 2>out2  | paste out2 -
1   e1
2   e2
3   e3
4   e4
5   e5

トラップを設定してfifoファイルを削除し、fifoファイルが存在するかどうかをテストしてから作成する必要があります。

私がテストしたすべてのシェル(Almquist構文と互換性があります)で移植可能に動作するようです。完全にテストされていないので、他のユーザーに確認を求めてください。まだ未知の驚きがあるかもしれません。

1
Isaac