パイプラインのプロセスを待機すると、間違った終了コードが返されます

パイプラインのプロセスを待機すると、間違った終了コードが返されます

ここで何かが抜けているに違いありません:

#!/bin/bash
timeout 5 sleep 10 &
parent_pid=$( ps --pid $! -o ppid --no-headers )
timeout_pid=$( pgrep --parent $parent_pid timeout )
wait $timeout_pid
echo "exit code: $?"

スクリプトを呼び出しwait-pidて実行します:

$ ./wait-pid 
exit code: 124

これは私が期待したとおりで、プロセスをtimeout強制終了しsleep、124 で終了し、wait忠実に戻ります。

しかし、最初のコマンドにパイプラインを追加すると、次のようになります。

timeout 5 sleep 10 | cat &
parent_pid=$( ps --pid $! -o ppid --no-headers )
timeout_pid=$( pgrep --parent $parent_pid timeout )
wait $timeout_pid
echo "exit code: $?"

今私は得ます:

$ ./wait-pid 
exit code: 0

wait待機中のプロセスの終了コードをまだ返すべきではありません: timeout。したがって、終了コードは依然として 124 であるべきではありません。

これはbash 4.4.20(1)リリースです。

答え1

私は、bash メーリング リストで非常に知識豊富な Greg Wooledge から回答を受け取りました。重要な要素は次のとおりです。

wait コマンドは、シェルの直接の子プロセス、つまり「非同期コマンド」でのみ機能します。非同期コマンドがパイプラインである場合は、元の sh 機能セットに戻ります。非同期コマンドの終了ステータスは、パイプラインの最後のコマンドの終了ステータスであり、他のコマンドの終了ステータスは単に破棄されます。

そのため、timeoutパイプライン内にある場合、終了コードを取得できません。

必要な動作を実現するには、一時ファイルを使用して、非同期コード内から終了コードをキャプチャします。次のようになります。

{
  timeout 5 sleep 10 | cat
  echo "${PIPESTATUS[@]}" > "$tempfile"
} &
wait

関連情報