2011-07-03 12 views
20

私はシェルスクリプトの関数間にジェネレータのようなパターンを使いたいと思っています。このような何か:パイプラインの左側に障害が発生しましたか?

parse_commands /da/cmd/file | process_commands 

しかし、このパターンの基本的な問題はparse_commandエラーが発生した場合、私はそれが失敗したことをprocess_command通知することを発見した唯一の方法は、明示的に(例えばエコー「FILE_NOT_FOUND」それを伝えることであるということです)。これは、parse_commandの潜在的なフォールト操作がすべてフェンスされなければならないことを意味します。

process_commandは、左側が非ゼロの終了コードで終了したことを検出する方法はありませんか?

+0

いつものように、2番目のグーグルがこれを見つけました。http://www.unix.com/302268337-post4.html – Bittrance

+1

http://stackoverflow.com/questions/32698407/kill-next-command-in-pipeline-on-失敗のbash/32699218#32699218は、この質問のやや鮮明なバージョンですが、間違いなく、より良い回答があります(質問は、パイプラインの右側に通知する方法を尋ねますが、受け入れられる回答は、親のシェルの状況)。 –

答えて

14

最初のプロセスが終了してもパイププロセスは続行するのですか、最初のプロセスが失敗したことを知らないという問題がありますか?

後者の場合は、PIPESTATUS変数(実際はBASH配列)を見ることができます。これにより、最初のコマンドの終了コードが得られます。

parse_commands /da/cmd/file | process_commands 
temp=("${PIPESTATUS[@]}") 
if [ ${temp[0]} -ne 0 ] 
then 
    echo 'parse_commands failed' 
elif [ ${temp[1]} -ne 0 ] 
then 
    echo 'parse_commands worked, but process_commands failed' 
fi 

それ以外の場合は、コプロセスを使用する必要があります。

+1

これは本当に情報を提供しますが、process_commandの内部では実行できないため、可読性が損なわれます。 – Bittrance

0

もしあなたがcommand1 && command2を持っていれば、command2は最初のコマンドが成功したときにのみ実行されます。そうでなければブールの短絡が起こります。これを使う方法の1つは、一時的にダンプする最初のコマンド(parse_commands...)そのファイルから2番目のコマンドを取得します。

編集:;を賢明に使用すると、一時ファイルを整理できます。オペレータ(& &)とは異なり

(command1 && command2) ; rm temporaryfile 
5

、パイプ演算子(|)が同時にプロセスを生成することによって機能し、第1のプロセスは、中間データをバッファリングする必要なく、第2のプロセスにパイプの出力をすることができます。これにより、メモリやディスクの使用量がほとんどない大量のデータを処理できます。

したがって、終了するまで、最初のプロセスの終了ステータスは2番目のプロセスで使用できなくなります。

+0

必ずしも真実ではありません。たとえば、http://stackoverflow.com/a/32699218/14122 –

+1

を参照してください。@Charlesまだ、終了した最初のプロセスの終了ステータスしか取得できないと言えます。しかし実際には、FIFOやPIPESTATUSのサウンドを使用して、さまざまな答えに応じて、適切な方法で終了ステータスにアクセスできるOP質問の汎用ソリューションのように聞こえます。 – jjmontes

4

あなたはFIFOを使用してarroundのいくつかの作業を試みることができる:

mkfifo /tmp/a 
cat /tmp/a | process_commands & 

parse_cmd /da/cmd/file > /tmp/a || (echo "error"; # kill process_commands) 
+0

ええ、これは、名前付きパイプではなく通常のパイプでのみ、ほぼ同じことをするCプログラムを書かずにできることだと思います。私はzsh coprocの施設がこれを助けるのだろうかと思うが、それについてはあまりよく分かっていない。 – andrewdski

0

灰からcoproc組み込みが追加されますbashの4.0でこれを行う方法は、あります。このコプロセッサー機能は、異なる構文を使用するkshから借用されています。コプロセッサをサポートする私のシステム上でアクセスできる唯一のシェルはkshです。ここでのkshで書かれた解決策は以下のとおりです。

parse_commands /da/cmd/file |& 
parser=$! 

process_commands <&p & 
processor=$! 

if wait $parser 
then 
    wait $processor 
    exit $? 
else 
    kill $processor 
    exit 1 
fi 

アイデアは、メインシェルにそれを接続する配管と、バックグラウンドでparse_commandsを開始することです。 pidはparserに保存されます。その後、入力としてparse_commandsの出力でprocess_commandsが開始されます。 (これは<&pのものです)。これは、pidがprocessorに保存されているバックグラウンドにも置かれます。

パイプで接続されているバックグラウンドの両方で、メインシェルはパーサーが終了するまで自由に待ちます。エラーなしで終了する場合は、プロセッサが終了してリターンコードで終了するまで待機します。 を終了してにエラーが発生した場合は、プロセッサを強制終了し、ゼロ以外のステータスで終了します。

bash 4.0/ash coprocの組み込み関数を使うためにこれを翻訳するのはかなり簡単ですが、私は良い文書もそれをテストする方法もありません。

+0

シグナルを受け取る前に 'process_commands'がEOFを打ち切らないようにするには(FIFOの書き込み終了時)どうしたらいいですか? –

0

あなたは、明示的なサブシェルでparse_commands /da/cmd/fileecho/dev/stdinに含まれているパイプで連結されたデータを処理するために、明示的なサブシェルで実行されprocess_commandsへのパイプを通してこのサブシェルの終了ステータスを実行することができます。ファーエレガントであることから

が、簡単な例

:)仕事を得るように見える:あなたのbashの上に

parse_commands /da/cmd/file > >(process_commands) 
0

何についてパイプの左側が失敗すると(終了ステータス!= 0)、右側が実行されないようにします。

+0

ここにはありません。 'false>>(/ bin/echo foo)' – xebeche

28

使用set -o pipefail

(
(ls -l ~/.bashrcxyz; echo $?) | 
( 
piped="$(</dev/stdin)"; 
[[ "$(tail -n 1 <<<"$piped")" -eq 0 ]] && printf '%s\n' "$piped" | sed '$d' || exit 77 
); 
echo $? 
) 
+7

"set -o pipefail"でテストすると、右側が実行されるのを防ぐことができないので、最初の質問に答えません。 "右側に通知する方法... "これは基本的にパイプの終了ステータスを変更します。デフォルトでは、パイプの終了ステータスは右側の終了ステータスです。このオプションはパイプコマンドの最大値です。 – mcoolive

+3

しかしそれは私が探していたものです:-)それは私を助けます。 Thx – mcoolive

+0

これはコマンド全体が失敗したように見えるでしょうか? – dtracers

2
私がコメントするのに十分な評判を持っていない

、しかしaccepted answerはこれを固定した後、5行目

に決算}がありませんでした、コードは、問題を指す-ne: unary operator expectedエラーをスローします:PIPESTATUSがありますifコマンドに続く条件で上書きされるため、戻り値process_commandsは決してチェックされません!

[ ${PIPESTATUS[0]} -ne 0 ]equivalent totest ${PIPESTATUS[0]} -ne 0であり、これは他のコマンドと同じように$PIPESTATUSに変わります。例:

return0() { return 0;} 
return3() { return 3;} 

return0 | return3 
echo "PIPESTATUS: ${PIPESTATUS[@]}" 

これは、期待通りにPIPESTATUS: 0 3を返します。条件式を導入すればどうでしょうか?私たちは、[: -ne: unary operator expectedエラーを取得しており、この

return0 | return3 
if [ ${PIPESTATUS[0]} -ne 0 ]; then 
    echo "1st command error: ${PIPESTATUS[0]}" 
elif [ ${PIPESTATUS[1]} -ne 0 ]; then 
    echo "2nd command error: ${PIPESTATUS[1]}" 
else 
    echo "PIPESTATUS: ${PIPESTATUS[@]}" 
    echo "Both return codes = 0." 
fi 

PIPESTATUS: 2 
Both return codes = 0. 

はこれを修正するには、$PIPESTATUSはそうのように、異なる配列変数に格納する必要があります。

return0 | return3 
TEMP=("${PIPESTATUS[@]}") 
echo "TEMP: ${TEMP[@]}" 
if [ ${TEMP[0]} -ne 0 ]; then 
    echo "1st command error: ${TEMP[0]}" 
elif [ ${TEMP[1]} -ne 0 ]; then 
    echo "2nd command error: ${TEMP[1]}" 
else 
    echo "TEMP: ${TEMP[@]}" 
    echo "All return codes = 0." 
fi 

印刷し、どの:

TEMP: 0 3 
2nd command error: 3 

を使用してください。

編集:私は受け入れられた答えを修正しましたが、私は後世のためにこの説明を残しています。

関連する問題