フィルターを介してerrpipe
を実行する単純なAPIを使用してユーティリティスクリプトstderr
を記述しようとしています。最初はbashのプロセス置換機能を使用して実装しようとしました。
#!/bin/bash
com="$1"
errpipe="$2"
$com 2> >(1>&2 $errpipe)
これの問題は、com
が存在しない場合に出力が奇妙に見えることです。
入力した場合
sh-3.2$ ./errpipe foo cat
私は得る
sh-3.2$ ./errpipe foo cat
sh-3.2$ ./errpipe: line 6: foo: command not found
@
@
カーソルを表します。つまり、シェルプロンプトの印刷が早すぎました。これは、メインのシェルスクリプトがプロセス置換プロセスが完了するのを待っていないためと考えられます。スクリプトの最後にwait
を入れても問題は解決しないようです。
bash
、ksh
、zsh
、またはいくつかのクレイジーなawk
機能を使用するソリューションを利用できます。プロセスやファイル記述子を操作するためのよりリッチなAPIを公開するCやPerlのようなものを使用してこれを一緒に配線する方法を知っていると思いますが、代替手段がない限り、それを使用したくないです。
「ほぼ機能する」1つの解決策は、$$
は、シェルがフォークしているときは変更されず、errpipeが終了したときに親に信号を送ります。
#!/bin/bash
com="$1"
errpipe="$2"
$com 2> >(1>&2 $errpipe; kill -SIGUSR1 $$)
while true; do
sleep 60
done
これは元の問題を修正しますが、a)醜く、b)印刷されますUser defined signal 1: 30
SIGUSR1のシグナルハンドラがある場合でも終了する前に、c)親にSIGUSRを送信するプロセスが何らかの理由で停止すると、c)は永久にループします。
はい、bash
(機能の由来)と同様にksh
では、プロセス置換内のプロセスは待機されません。
<(...)
の場合、通常は次のように問題ありません。
cmd1 <(cmd2)
シェルはcmd1
を待機し、cmd1
は通常、置換されるパイプのファイルの終わりまで読み取ることによってcmd2
を待機します。通常、ファイルの終わりはcmd2
が終了したときに発生します。これは、いくつかのシェル(bash
ではない)が、cmd2
でcmd2 | cmd1
を待たない理由と同じです。
ただし、cmd1 >(cmd2)
の場合、通常はそうではありません。通常、cmd2
を待機するのは、cmd1
の方が多いため、通常、後で終了します。
zsh
はcmd2
を待機します(ただし、cmd1 > >(cmd2)
と記述した場合は、代わりに{cmd1} > >(cmd2)
を documented として使用してください)。
ksh
はデフォルトでは待機しませんが、wait
ビルトインで待機することができます($!
でpidを使用できるようになりますが、cmd1 >(cmd2) >(cmd3)
を実行しても役に立ちません)
rc
(cmd1 >{cmd2}
構文を使用)、$apids
を使用してすべてのバックグラウンドプロセスのPIDを取得できることを除いて、ksh
と同じです。
es
(およびcmd1 >{cmd2}
も使用)は、zsh
と同様にcmd2
を待機し、cmd2
プロセスリダイレクトで<{cmd2}
を待機します。
bash
は、cmd2
のpidをcmd2
で利用できるようにします(または、サブシェルの子プロセスで$!
を実行するため、サブシェルで正確に実行できます)。
bash
を使用する必要がある場合は、次のコマンドで両方のコマンドを待機するコマンドを使用して問題を回避できます。
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
これにより、cmd1
とcmd2
の両方で、パイプに対してfd 3が開かれます。 cat
は、もう一方の端でファイルの終わりを待機するため、通常は、cmd1
とcmd2
の両方がデッドである場合にのみ終了します。そして、シェルはそのcat
コマンドを待ちます。すべてのバックグラウンドプロセスの終了をキャッチするネットとして見ることができます(&
、coprocs、またはデーモンなどのすべてのファイル記述子を閉じない限り、バックグラウンドで開始されたコマンドなど、バックグラウンドで開始された他のものに使用できます。行う)。
上記の無駄なサブシェルプロセスのおかげで、cmd2
がそのfd 3を閉じても機能することに注意してください(コマンドは通常それを行わないが、Sudo
やssh
のように行う)。 bash
の将来のバージョンでは、他のシェルと同様に、最終的に最適化が行われる可能性があります。次に、次のようなものが必要になります。
{ { cmd1 >(Sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
そのfd 3を開いて、そのSudo
コマンドを待機する追加のシェルプロセスがまだあることを確認します。
cat
は何も読み取らないことに注意してください(プロセスがfd 3に書き込みを行わないため)。同期のためだけにあります。最後に何も返さないread()
システムコールを1つだけ実行します。
コマンド置換を使用してパイプ同期を行うことにより、cat
の実行を実際に回避できます。
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
今回は、cmd1
およびcmd2
のfd 3で他端が開いているパイプから読み取るのは、cat
ではなくシェルです。変数割り当てを使用しているので、cmd1
の終了ステータスは$?
で利用できます。
または、手動でプロセスの置換を行い、システムのsh
を使用して、標準のシェル構文にすることもできます。
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
ただし、前述のように、cmd1
が終了した後、sh
のすべての実装がcmd2
を待機するわけではないことに注意してください(逆の方法よりも優れています)。そのとき、$?
にはcmd2
の終了ステータスが含まれます。ただし、bash
とzsh
は、cmd1
と${PIPESTATUS[0]}
でそれぞれ$pipestatus[1]
の終了ステータスを使用できるようにします(いくつかのシェルのpipefail
オプションも参照して、$?
が最後以外のパイプコンポーネントの障害を報告できるようにします)。
yash
のプロセスredirection機能に関しても同様の問題があることに注意してください。 cmd1 >(cmd2)
はcmd1 /dev/fd/3 3>(cmd2)
と記述されます。ただし、cmd2
は待機されず、wait
を使用して待機することもできません。また、$!
変数でそのpidも使用できません。 bash
と同じ回避策を使用します。