1. ホーム
  2. shell

[解決済み] シェルパイプでエラーコードをキャッチする

2022-10-24 02:05:23

質問

現在、次のようなスクリプトを持っています。

./a | ./b | ./c

a,b,cのいずれかがエラーコードで終了した場合、エラーメッセージを表示し、悪い出力を前方にパイプする代わりに停止するように変更したいです。

そうするために最もシンプルでクリーンな方法は何でしょうか?

どのように解決するのですか?

最初のコマンドが成功したとわかるまで、2 番目のコマンドを実行させたくない場合は、おそらく一時ファイルを使用する必要があります。 そのシンプルなバージョンは

tmp=${TMPDIR:-/tmp}/mine.$$
if ./a > $tmp.1
then
    if ./b <$tmp.1 >$tmp.2
    then
        if ./c <$tmp.2
        then : OK
        else echo "./c failed" 1>&2
        fi
    else echo "./b failed" 1>&2
    fi
else echo "./a failed" 1>&2
fi
rm -f $tmp.[12]

1>&2' リダイレクトは '>&2' とも省略できますが、MKS シェルの古いバージョンでは、前の '1' がないとエラーリダイレクトを誤って処理するので、昔からこのあいまいでない表記を信頼性のために使ってきました。

これは何かを中断するとファイルが漏れる。 防爆型(多かれ少なかれ)シェルプログラミングの使い方。

tmp=${TMPDIR:-/tmp}/mine.$$
trap 'rm -f $tmp.[12]; exit 1' 0 1 2 3 13 15
...if statement as before...
rm -f $tmp.[12]
trap 0 1 2 3 13 15

最初のトラップ行は、「コマンドを実行する」と書かれています。 rm -f $tmp.[12]; exit 1 ' のいずれかのシグナル 1 SIGHUP、2 SIGINT、3 SIGQUIT、13 SIGPIPE、または 15 SIGTERM が発生したとき、または 0 (シェルが何らかの理由で終了したとき) が発生したときです。 シェルスクリプトを書いている場合、最終的なトラップはシェル終了トラップである 0 のトラップのみを削除する必要があります (どのみちプロセスは終了しようとしているので、他のシグナルをそのままにしておくことができます)。

元のパイプラインでは、'a' が終了する前に 'c' が 'b' からデータを読み取ることは可能です。これは通常望ましいことです (たとえば、複数のコアに仕事をさせることになります)。 b'が'sort'フェーズである場合、これは適用されません - 'b'はその出力のいずれかを生成する前にそのすべての入力を見なければなりません。

どのコマンド(複数可)が失敗したかを検出したい場合、使用できます。

(./a || echo "./a exited with $?" 1>&2) |
(./b || echo "./b exited with $?" 1>&2) |
(./c || echo "./c exited with $?" 1>&2)

これはシンプルで対称的です。4分割やN分割のパイプラインに拡張するのは些細なことです。

set -e」による単純な実験では役に立ちませんでした。