回答:
最初のコマンドが成功するまで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のトラップのみを削除する必要があります(プロセスはとにかく終了するため、他のシグナルをそのままにしておくことができます)。
元のパイプラインでは、「c」が「a」が終了する前に「b」からデータを読み取ることが可能です。これは通常望ましいことです(たとえば、複数のコアが実行する作業を提供します)。「b」が「ソート」フェーズの場合、これは適用されません。「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'を使用した単純な実験では効果がありませんでした。
mktemp
またはの使用をお勧めしtempfile
ます。
trap
する場合、ユーザーはいつでもSIGKILL
プロセスに送信してすぐに終了できることに注意してください。その場合、トラップは有効になりません。システムで停電が発生した場合も同様です。一時ファイルを作成するときは、必ず使用してmktemp
ください。これにより、ファイルがどこかに置かれ、再起動後(通常はに/tmp
)クリーンアップされます。
bashを使用できset -e
とset -o pipefail
、ファイルの先頭に。./a | ./b | ./c
3つのスクリプトのいずれかが失敗すると、後続のコマンドは失敗します。戻りコードは、最初に失敗したスクリプトの戻りコードになります。
pipefail
標準のshでは使用できないことに注意してください。
set
(合計4つの異なるバージョンの)の一方または両方を削除し、それが最後のの出力にどのように影響するかを確認することecho
です。
${PIPESTATUS[]}
完全に実行した後で配列を確認することもできます。
./a | ./b | ./c
次に${PIPESTATUS}
、パイプ内の各コマンドからのエラーコードの配列になるため、中間のコマンドが失敗した場合echo ${PIPESTATUS[@]}
は、次のようになります。
0 1 0
そして、このような何かがコマンドの後に実行されます:
test ${PIPESTATUS[0]} -eq 0 -a ${PIPESTATUS[1]} -eq 0 -a ${PIPESTATUS[2]} -eq 0
パイプ内のすべてのコマンドが成功したことを確認できます。
#!/bin/sh
、を起動するスクリプトで使用しようとすると、問題が発生する可能性があります。これは、sh
bashでないと機能しないためです。(#!/bin/bash
代わりに使用することを覚えておけば、簡単に修正できます。)
echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'
$PIPESTATUS[@]
-0とスペースのみが含まれる場合は0を返します(パイプ内のすべてのコマンドが成功した場合)。
command1 && command2 | command3
ます。たとえば、これらのいずれかが失敗した場合、ソリューションはゼロ以外を返します。
残念ながら、ジョナサンの回答には一時ファイルが必要で、ミシェルとイムロンの回答にはbashが必要です(この質問にはシェルというタグが付けられていますが)。他の人がすでに指摘したように、後のプロセスが開始される前にパイプを中止することはできません。すべてのプロセスは一度に開始されるため、エラーが通知される前にすべて実行されます。しかし、質問のタイトルはエラーコードについても尋ねていました。これらは、パイプが終了した後に取得および調査して、関連するプロセスのいずれかが失敗したかどうかを確認できます。
これは、最後のコンポーネントのエラーだけでなく、パイプ内のすべてのエラーをキャッチするソリューションです。したがって、これはbashのpipefailのようなものであり、すべてのエラーコードを取得できるという意味でより強力です。
res=$( (./a 2>&1 || echo "1st failed with $?" >&2) |
(./b 2>&1 || echo "2nd failed with $?" >&2) |
(./c 2>&1 || echo "3rd failed with $?" >&2) > /dev/null 2>&1)
if [ -n "$res" ]; then
echo pipe failed
fi
何かが失敗したかどうかを検出するためにecho
、コマンドが失敗した場合、コマンドは標準エラーに出力します。次に、結合された標準エラー出力が保存され$res
、後で調査されます。これは、すべてのプロセスの標準エラーが標準出力にリダイレクトされる理由でもあります。また、その出力を送信し/dev/null
たり、問題が発生したことを示す別のインジケータとして残したりすることもできます。/dev/null
最後のコマンドの出力をどこかに保存する必要がない場合は、最後のリダイレクト先をファイルに置き換えることができます。
この構築物で、より再生するには、これは本当に、私は置き換え何それが必要ないことを自分自身を納得させるために./a
、./b
そして./c
実行サブシェルによってecho
、cat
およびexit
。これを使用して、この構成が実際にすべての出力を1つのプロセスから別のプロセスに転送すること、およびエラーコードが正しく記録されることを確認できます。
res=$( (sh -c "echo 1st out; exit 0" 2>&1 || echo "1st failed with $?" >&2) |
(sh -c "cat; echo 2nd out; exit 0" 2>&1 || echo "2nd failed with $?" >&2) |
(sh -c "echo start; cat; echo end; exit 0" 2>&1 || echo "3rd failed with $?" >&2) > /dev/null 2>&1)
if [ -n "$res" ]; then
echo pipe failed
fi
&&|
「前のコマンドが成功した場合にのみパイプを続行する」ことを意味するようなものが本当に必要です。|||
「前のコマンドが失敗した場合にパイプを続行する」という意味もあるかもしれません(そしておそらくBash 4のようなエラーメッセージをパイプします|&
)。