web-dev-qa-db-ja.com

コマンドが無効な場合でも、bashはプロセス内のプロセス置換を待機します

フィルターを介して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を入れても問題は解決しないようです。

bashkshzsh、またはいくつかのクレイジーな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)は永久にループします。

9
Gregory Nisbet

はい、bash(機能の由来)と同様にkshでは、プロセス置換内のプロセスは待機されません。

<(...)の場合、通常は次のように問題ありません。

cmd1 <(cmd2)

シェルはcmd1を待機し、cmd1は通常、置換されるパイプのファイルの終わりまで読み取ることによってcmd2を待機します。通常、ファイルの終わりはcmd2が終了したときに発生します。これは、いくつかのシェル(bashではない)が、cmd2cmd2 | cmd1を待たない理由と同じです。

ただし、cmd1 >(cmd2)の場合、通常はそうではありません。通常、cmd2を待機するのは、cmd1の方が多いため、通常、後で終了します。

zshcmd2を待機します(ただし、cmd1 > >(cmd2)と記述した場合は、代わりに{cmd1} > >(cmd2)documented として使用してください)。

kshはデフォルトでは待機しませんが、waitビルトインで待機することができます($!でpidを使用できるようになりますが、cmd1 >(cmd2) >(cmd3)を実行しても役に立ちません)

rccmd1 >{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

これにより、cmd1cmd2の両方で、パイプに対してfd 3が開かれます。 catは、もう一方の端でファイルの終わりを待機するため、通常は、cmd1cmd2の両方がデッドである場合にのみ終了します。そして、シェルはそのcatコマンドを待ちます。すべてのバックグラウンドプロセスの終了をキャッチするネットとして見ることができます(&、coprocs、またはデーモンなどのすべてのファイル記述子を閉じない限り、バックグラウンドで開始されたコマンドなど、バックグラウンドで開始された他のものに使用できます。行う)。

上記の無駄なサブシェルプロセスのおかげで、cmd2がそのfd 3を閉じても機能することに注意してください(コマンドは通常それを行わないが、Sudosshのように行う)。 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の終了ステータスが含まれます。ただし、bashzshは、cmd1${PIPESTATUS[0]}でそれぞれ$pipestatus[1]の終了ステータスを使用できるようにします(いくつかのシェルのpipefailオプションも参照して、$?が最後以外のパイプコンポーネントの障害を報告できるようにします)。

yashのプロセスredirection機能に関しても同様の問題があることに注意してください。 cmd1 >(cmd2)cmd1 /dev/fd/3 3>(cmd2)と記述されます。ただし、cmd2は待機されず、waitを使用して待機することもできません。また、$!変数でそのpidも使用できません。 bashと同じ回避策を使用します。

13