別のパイプに接続されたプロセスの終了ステータスを取得する


回答:


256

あなたが使用している場合bashは、使用することができますPIPESTATUSパイプラインの各要素の終了ステータスを取得するために、配列変数を。

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

を使用している場合zsh、配列が呼び出されpipestatus(大文字と小文字が区別されます!)、配列のインデックスは1から始まります。

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

値を失わない方法で関数内でそれらを結合するには:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

bashまたはzshで上記を実行すると、同じ結果が得られます。1だけretval_bashretval_zsh設定されます。もう一方は空白になります。これにより、関数を終了できますreturn $retval_bash $retval_zsh(引用符がないことに注意してください!)。


9
そしてpipestatuszshで。残念ながら、他のシェルにはこの機能がありません。
ジル

8
注:zshの配列は、インデックス1から直感に反して始まるため、echo "$pipestatus[1]" "$pipestatus[2]"です。
クリストフワーム

5
次のようにパイプライン全体を確認できますif [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
。– l0b0

7
@JanHudec:おそらく、あなたは私の答えの最初の5つの単語を読むべきでしょう。また、質問がPOSIXのみの回答を要求した場所を親切に指摘してください。
CAMH

12
@JanHudec:POSIXタグも付けられていません。答えがPOSIXでなければならないのはなぜだと思いますか?指定されていないため、適格な回答を提供しました。私の答えに間違いはありません。また、他のケースに対処するための複数の答えがあります。
CAMH

238

これを行うには、3つの一般的な方法があります。

パイプフェイル

最初の方法は、pipefailオプション(kshzshまたはbash)を設定することです。これは最も単純で、基本的には、終了ステータス$?を最後のプログラムの終了コードに設定して、ゼロ以外(またはすべてが正常に終了した場合はゼロ)に終了します。

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

Bashには、最後のパイプラインのすべてのプログラムの終了ステータスを含む$PIPESTATUS$pipestatusin zsh)という配列変数もあります。

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

3番目のコマンド例を使用して、必要なパイプラインの特定の値を取得できます。

個別の実行

これが最も扱いにくいソリューションです。各コマンドを個別に実行し、ステータスをキャプチャします

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1

2
くそー!PIPESTATUSについて投稿するつもりでした。
SLMの

1
参考のために、このSOの質問で説明されている他の手法がいくつかあります。stackoverflow.com
slm

1
@Patrick pipestatusソリューションはbashで動作します。kshスクリプトを使用する場合、pipestatusに似たものを見つけることができると思いますか?、(私はkshによってサポートされていないpipestatusを見る)
-yael

2
@yaelは使用しkshていませんが、マンページを少し見ただけではサポートされていません$PIPESTATUSpipefailただし、このオプションはサポートされています。
パトリック

1
私はそれは私がここで失敗したコマンドのステータスを取得することを可能にするようpipefailで行くことにしました:LOG=$(failed_command | successful_command)
vmrob

51

このソリューションは、bash固有の機能または一時ファイルを使用せずに機能します。ボーナス:最終的に、終了ステータスは実際には終了ステータスであり、ファイル内の文字列ではありません。

状況:

someprog | filter

からの終了ステータスとからsomeprogの出力が必要filterです。

ここに私の解決策があります:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

この構成の結果は、構成のstdoutからのfilterstdoutであり、構成の終了ステータスsomeprogとしてのexitステータスです。


この構成{...}は、サブシェルの代わりに単純なコマンドグループ化でも機能し(...)ます。サブシェルには、特にパフォーマンスコストが含まれますが、ここでは必要ありません。詳細については、詳細なbashマニュアルを参照してください:https : //www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

残念ながら、bashの文法では、中括弧にスペースとセミコロンが必要なので、構造がより広くなります。

このテキストの残りの部分では、サブシェルバ​​リアントを使用します。


someprogfilter

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

出力例:

filtered line1
filtered line2
filtered line3
42

注:子プロセスは、親から開いているファイル記述子を継承します。これは、someprog開いているファイル記述子3と4を継承することを意味someprogします。ファイル記述子3に書き込むと、終了状態になります。read一度しか読み取らないため、実際の終了ステータスは無視されます。

あなたはあなたのことを心配した場合someprog、ディスクリプタ3または4をファイルに書き込めかもしれない、それが呼び出す前に、ファイルディスクリプタをクローズするのが最善ですsomeprog

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

exec 3>&- 4>&-前にsomeprog実行する前に、ファイルディスクリプタをクローズするsomeprogためので、someprogこれらのファイルディスクリプタは、単に存在しません。

次のように書くこともできます。 someprog 3>&- 4>&-


コンストラクトのステップごとの説明:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

下から上へ:

  1. サブシェルは、stdoutにリダイレクトされるファイル記述子4で作成されます。これは、サブシェルのファイル記述子4に出力されるものはすべて、コンストラクト全体の標準出力になることを意味します。
  2. パイプが作成され、左(#part3)および右(#part2)のコマンドが実行されます。exit $xsまた、パイプの最後のコマンドであり、これは、stdinからの文字列が構造全体の終了ステータスになることを意味します。
  3. サブシェルが作成され、ファイル記述子3がstdoutにリダイレクトされます。これは、このサブシェルのファイル記述子3に出力されるものはすべて、最終#part2的には構造全体の終了ステータスになることを意味します。
  4. パイプが作成され、左(#part5および#part6)および右(filter >&4)のコマンドが実行されます。の出力は、ファイル記述子4にリダイレクトされますfilter。ファイル記述子4では#part1、stdoutにリダイレクトされました。これは、の出力がfilter構造全体の標準出力であることを意味します。
  5. 終了状態#part6で記述子3をファイルに出力された#part3ファイルディスクリプタ3はにリダイレクトされました#part2。これは、からの#part6終了ステータスが、構造全体の最終的な終了ステータスになることを意味します。
  6. someprog実行されます。終了ステータスはに取り込まれ#part5ます。stdoutはパイプに取り込まれ、に#part4転送されfilterます。からの出力filterは、次に説明するように、stdoutに到達します。#part4

1
いいね 私はちょうどするかもしれない機能について(read; exit $REPLY)
-jthill

5
(exec 3>&- 4>&-; someprog)に簡素化しsomeprog 3>&- 4>&-ます。
ロジャーパテ

また、このメソッドは、サブシェルせずに動作します:{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
josch

36

正確にあなたが尋ねたものではありませんが、使用することができます

#!/bin/bash -o pipefail

パイプが最後の非ゼロリターンを返すようにします。

コーディングが少し少ないかもしれません

編集:例

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1

9
set -o pipefailたとえば、誰かがを介してスクリプトを実行する場合に備えて、スクリプト内の堅牢性を高めてくださいbash foo.sh
-maxschlepzig

それはどのように機能しますか?例はありますか?
ヨハン

2
-o pipefailPOSIXにはないことに注意してください。
y

2
これはBASH 3.2.25(1)-releaseでは機能しません。/ tmp / ffの一番上にあり#!/bin/bash -o pipefailます。エラー:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
フェリペアルバレス14年

2
@FelipeAlvarez:一部の環境(Linuxを含む)は#!、最初の行を超える行のスペースを解析しないため、これ/bin/bash -o pipefail /tmp/ffは必要ではなくになります/bin/bash -o pipefail /tmp/ff- getopt(または同様の)を引数として使用してoptarg、の次の項目を解析しARGVますから-o、失敗します。あなたは、ラッパー(発言権を作るとしたらbash-pfちょうどやっていることexec /bin/bash -o pipefail "$@"、および上でそれを置く#!ライン、それがうまくいく参照:。en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes

22

可能であれば、終了コードをにフィードfoobarます。たとえば、foo数字だけの行を生成しないことがわかっている場合は、終了コードを追加するだけです。

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

または、からの出力にfoo決して次の行が含まれないことがわかっている場合.

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

これはbar、最後の行を除くすべての行で動作する方法があり、最後の行を終了コードとして渡す場合に常に実行できます。

bar出力が不要な複雑なパイプラインの場合は、別のファイル記述子に終了コードを出力することにより、その一部をバイパスできます。

exit_codes=$({ { foo; echo foo:"$?" >&3; } |
               { bar >/dev/null; echo bar:"$?" >&3; }
             } 3>&1)

この後は$exit_codes通常であるfoo:X bar:Yが、それは可能性がbar:Y foo:Xあればbarその入力の全てを読む前にまたはあなたが不運なら終了します。512バイトまでのパイプへの書き込みはすべてのユニックスでアトミックであるため、タグ文字列が507バイト未満である限り、foo:$?とのbar:$?部分は混在しません。

からの出力をキャプチャする必要がある場合barは、難しくなります。bar終了コード表示のように見える行を決して含まないように出力を調整することにより、上記の手法を組み合わせることができますが、手間がかかります。

output=$(echo;
         { { foo; echo foo:"$?" >&3; } |
           { bar | sed 's/^/^/'; echo bar:"$?" >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

そして、もちろん、一時ファイル使用してステータスを保存する簡単なオプションがあります。シンプルではなく、その生産にシンプル:

  • 複数のスクリプトが同時に実行されている場合、または同じスクリプトが複数の場所でこのメソッドを使用している場合、異なる一時ファイル名を使用することを確認する必要があります。
  • 共有ディレクトリに一時ファイルを安全に作成するのは困難です。多くの場合、/tmpスクリプトがファイルを書き込むことができる唯一の場所です。使用mktemp頃は、すべての深刻なUnix系OS上でPOSIXが、利用できません。
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
一時ファイルアプローチを使用する場合、スクリプトが
停止し

17

パイプラインから開始:

foo | bar | baz

POSIXシェルのみを使用し、一時ファイルを使用しない一般的なソリューションを次に示します。

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$error_statuses 失敗したプロセスのステータスコードをランダムな順序で含み、どのコマンドが各ステータスを発行したかを示すインデックスが含まれます。

# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null

$error_statuses私のテストで引用符に注意してください。grep改行がスペースに強制されるため、それらなしでは区別できません。


12

だから私はレスマナのような答えを提供したかったのですが、おそらく私のほうが少しシンプルで少し有利な純ボーンシェルソリューションだと思います:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

これは内側から最もよく説明されていると思います-command1は通常の出力を実行してstdout(ファイル記述子1)に出力します。ファイル記述子3。

command1の実行中、そのstdoutはcommand2にパイプされます(printfの出力は、パイプの読み取り値である1ではなくファイル記述子3に送信するため、command2にはなりません)。次に、command2の出力をファイル記述子4にリダイレクトします。これにより、ファイル記述子1から離れたままになります。 1 –コマンド置換(バックティック)がキャプチャし、変数に配置されるためです。

魔法の最後のビットは、最初exec 4>&1に別のコマンドとして行ったことです。これは、外部シェルの標準出力のコピーとしてファイル記述子4を開きます。コマンド置換は、その中のコマンドの観点から標準出力に書かれたものをすべてキャプチャしますが、コマンド置換に関する限り、command2の出力はファイル記述子4に送られるため、コマンド置換はキャプチャしません–コマンド置換の「アウト」を取得すると、スクリプトのファイル記述子全体1に効果的に移動します。

exec 4>&1多くの一般的なシェルは、置換を使用している「外部」コマンドで開かれているコマンド置換内でファイル記述子に書き込もうとすると、それが気に入らないため、別のコマンドである必要があります。最も簡単で移植可能な方法です。)

コマンドの出力が互いに跳躍しているように、技術的ではなく遊び心のある方法で見ることができます.command1はcommand2にパイプし、printfの出力はcommand2を飛び越えてcommand2がキャッチしないようにし、コマンド2の出力は、置換でキャプチャされるのにちょうど間に合うようにprintfが着陸するようにコマンド置換から飛び出して、変数で終わるようになり、コマンド2の出力は標準出力に書き込まれるようになります。通常のパイプで。

また、私が理解しているように、$?変数の割り当て、コマンドの置換、複合コマンドはすべて、その中のコマンドのリターンコードに対して事実上透過的であるため、パイプ内の2番目のコマンドのリターンコードが含まれます。 command2は伝播されるはずです。これは、追加の関数を定義する必要がないので、lesmanaが提案したものよりもいくらか優れたソリューションであると思う理由です。

lesmanaの注意事項によれば、command1はある時点でファイル記述子3または4を使用する可能性があるため、より堅牢にするために次のようにします。

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

この例では複合コマンドを使用していますが、サブシェル(( )代わりに{ }を使用しても機能しますが、効率が低下する可能性があります)

コマンドはそれらを起動するプロセスからファイル記述子を継承するため、2行目全体がファイル記述子4 3>&1を継承し、その後に続く複合コマンドがファイル記述子3を継承します。そのため4>&-、内部複合コマンドがファイル記述子4を3>&-継承せず、ファイル記述子3を継承しないようにするため、command1はよりクリーンな、より標準的な環境を取得します。内側をの4>&-隣に移動することもできますが3>&-、なぜその範囲をできるだけ制限しないのかを理解しています。

どのくらいの頻度でファイル記述子3と4を直接使用するのかわかりません。ほとんどの場合、プログラムは未使用のファイル記述子を返すsyscallsを使用すると思いますが、コードはファイル記述子3に直接書き込みます。推測(プログラムがファイル記述子をチェックして開いているかどうかを確認し、開いている場合はそれを使用し、そうでない場合はそれに応じて異なる動作をすることを想像できます)。したがって、後者はおそらく、念頭に置いて汎用ケースに使用するのが最善です。


おもしろそうに見えますが、このコマンドが何を期待しているのかわかりませんし、私のコンピューターもできません。私は得る-bash: 3: Bad file descriptor
Gマン

@ G-Manそうですね、私は、通常使用するシェル(busyboxに付属する灰)とは異なり、ファイル記述子に関してはbashが何をしているかわからないことを忘れません。bashを幸せにする回避策を考えたときにお知らせします。とりあえず、debian boxを手元に置いておくとダッシュで試すことができます。busyboxを手元に置いておくとbusybus ash / shで試すことができます。
mtraceur

@ G-Manコマンドが行うことを期待していること、および他のシェルで行うことについては、command1からstdoutをリダイレクトするため、コマンド置換によってキャッチされませんが、一度コマンド置換の外に出ると、ドロップしますfd3がstdoutに戻るので、コマンド2に期待どおりにパイプされます。command1が終了すると、printfが起動して終了ステータスを出力します。これは、コマンド置換によって変数にキャプチャされます。ここで非常に詳細な内訳:stackoverflow.com/questions/985876/tee-and-exit-status / ...また、あなたのコメントは、まるでit辱するつもりであるかのように読みましたか?
mtraceur

どこから始めましょうか?(1)you辱されたとしたら申し訳ありません。「面白そうだ」とは真剣に意味していました。あなたが期待したように、それがそれと同じくらいコンパクトに機能するなら、それは素晴らしいことです。それを超えて、私は、単純に、あなたのソリューションが何をすべきかを理解していなかったと言っていました。私は長い間(Linuxが存在する前から)Unixで働いたり遊んだりしており、もし何かを理解していなければそれはおそらく他の人もそれを理解できないという赤い旗であり、詳細な説明が必要です(IMNSHO)。…(続き)
Gマン

(続き)… あなたは「…普通の人以上のことすべてを理解している」と考えているので、Stack Exchangeの目的はコマンドを書くサービスではなく、些細な質問に対する数千もの一回限りのソリューション。むしろ、人々に自分の問題を解決する方法を教えることです。そして、そのためには、「平均的な人」が理解できるほど十分に説明する必要があるかもしれません。優れた説明の例については、レスマナの答えをご覧ください。…(続き)
Gマン


7

上記のlesmanaのソリューションは、{ .. }代わりにを使用して、ネストされたサブプロセスを開始するオーバーヘッドなしで実行することもできます(グループ化されたコマンドのこの形式は常にセミコロンで終了する必要があることを思い出してください)。このようなもの:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

ダッシュバージョン0.5.5とbashバージョン3.2.25および4.2.42でこの構造を確認したため、一部のシェルが{ .. }グループ化をサポートしていない場合でも、POSIXに準拠しています。


これは、NetBSD sh、pdksh、mksh、dash、bashなど、私が試したほとんどのシェルで非常にうまく機能します。ただしset -o pipefail、kshまたは任意の数のスプリンクルwaitコマンドを使用しても、AT&T Ksh(93s +、93u +)またはzsh(4.3.9、5.2)で動作させることはできません。サブシェルの使用に固執するとうまくいくように、少なくとも部分的にはkshの解析の問題かもしれないと思いますが、ksh ifのサブシェルバ​​リアントを選択しても、他の人のために複合コマンドを残すと、失敗します。
グレッグA.ウッズ

4

これは移植性があります。つまり、POSIX準拠のシェルで動作し、現在のディレクトリを書き込み可能にする必要がなく、同じトリックを使用する複数のスクリプトを同時に実行できます。

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

編集:これは、Gillesのコメントに続くより強力なバージョンです:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2:これは、dubiousjimのコメントに続いてわずかに軽いバリアントです。

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))

3
これはいくつかの理由で機能しません。1.一時ファイルは、書き込まれる前に読み取られる場合があります。2.共有ディレクトリに予測可能な名前で一時ファイルを作成することは安全ではありません(簡単なDoS、シンボリックリンクの競合)。3.同じスクリプトがこのトリックを数回使用する場合、常に同じファイル名を使用します。1を解決するには、パイプラインが完了した後にファイルを読み取ります。2と3を解決するには、ランダムに生成された名前またはプライベートディレクトリにある一時ファイルを使用します。
ジル

+1さて、$ {PIPESTATUS [0]}の方が簡単ですが、ここでの基本的な考え方は、Gillesが言及している問題について知っていれば機能します。
ヨハン

いくつかのサブシェルを保存できます(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))。@Johan:Bashの方が簡単だと思いますが、状況によっては、Bashを回避する方法を知っておく価値があります。
-dubiousjim

4

以下は、一般的なソリューションのいずれかを使用できない場合に備えて、@ Patrikの回答に対するアドオンとして使用されます。

この回答では、次のことを前提としています。

  • あなたが知っていないシェルを持ってい$PIPESTATUSたりset -o pipefail
  • 並列実行にはパイプを使用するため、一時ファイルは使用しません。
  • 突然の停電などでスクリプトを中断した場合、余分な混乱を避けたいでしょう。
  • このソリューションは、比較的簡単に読みやすく、読みやすいものでなければなりません。
  • 追加のサブシェルを導入したくない場合。
  • 既存のファイル記述子をいじることはできないため、stdin / out / errに手を触れないでください(ただし、一時的に新しい記述子を導入することはできます)

追加の仮定。あなたはすべてを取り除くことができますが、これはレシピを過剰に破壊するため、ここでは説明しません:

  • 知りたいのは、PIPEのすべてのコマンドに終了コード0があることだけです。
  • 追加のサイドバンド情報は必要ありません。
  • シェルは、すべてのパイプコマンドが戻るまで待機します。

Before foo | bar | baz:、ただし、これは最後のコマンドの終了コードのみを返します(baz

指名手配:パイプ内のいずれかのコマンドが失敗した場合、(true)であって$?はなりません0

後:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

# $? now is 0 only if all commands had exit code 0

説明:

  • 一時ファイルはで作成されmktempます。これは通常、すぐにファイルを作成します/tmp
  • この一時ファイルは、書き込み用にFD 9にリダイレクトされ、読み取り用にFD 8にリダイレクトされます
  • その後、一時ファイルはすぐに削除されます。ただし、両方のFDが存在しなくなるまで、開いたままになります。
  • これでパイプが開始されました。エラーが発生した場合、各ステップはFD 9にのみ追加されます。
  • wait以下のために必要とされているkshので、ksh他にはフィニッシュにすべてのパイプコマンドを待ちません。ただし、バックグラウンドタスクが存在する場合、不要なsideffectsがあることに注意してください。そのため、デフォルトでコメントアウトしました。待機に支障がない場合は、コメントしてください。
  • その後、ファイルの内容が読み取られます。空の場合(すべて機能しているため)readはを返しfalsetrueエラーを示します

これは、単一のコマンドのプラグインの置換として使用でき、以下のみが必要です。

  • 未使用のFD 9および8
  • 一時ファイルの名前を保持する単一の環境変数
  • そして、このレシピは、IOリダイレクトを可能にするあらゆるシェルに適応させることができます
  • また、かなりプラットフォームに依存せず、次のようなものを必要としません /proc/fd/N

バグ:

このスクリプトには、/tmpスペースが不足した場合のバグがあります。この人為的なケースに対する保護も必要な場合は、次のように行うことができますが、これには、パイプ内のコマンドの数0000応じてin の数が決まるという欠点があります。

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

移植性に関する注意:

  • kshそして最後のパイプコマンドを待つだけの同様のシェルは、waitコメントを外す必要があります

  • 最後の例は、これがより移植性がprintf "%1s" "$?"あるecho -n "$?"ため、代わりに使用します。すべてのプラットフォームが-n正しく解釈されるわけではありません。

  • printf "$?"しかし、printf "%1s"実際に壊れたプラットフォームでスクリプトを実行する場合に、いくつかのコーナーケースをキャッチします。(読む:たまたまプログラムを作成する場合paranoia_mode=extreme

  • FD 8およびFD 9は、複数の数字をサポートするプラットフォームでより高くなる可能性があります。AFAIR a POSIX準拠のシェルは、1桁の数字のみをサポートする必要があります。

  • Debianの8.2でテストされたshbashkshashsashとさえcsh


3

少し予防策を講じれば、これはうまくいくはずです:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)

jlliagreのようなクリーンアップはいかがですか?foo-statusというファイルを残しませんか?
ヨハン

@Johan:あなたが私の提案を好むなら、それを投票することをheしないでください;)ファイルを残さないことに加えて、複数のプロセスがこれを同時に実行できるという利点があり、現在のディレクトリは書き込み可能である必要はありません。
jlliagre

2

次の「if」ブロックは、「command」が成功した場合にのみ実行されます。

if command; then
   # ...
fi

具体的には、次のようなものを実行できます。

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

実行されhaconf -makerw、stdoutとstderrが「$ haconf_out」に保存されます。からの戻り値haconfがtrueの場合、「if」ブロックが実行され、grep「$ haconf_out」が読み取られ、「Cluster already writable」と照合されます。

パイプは自動的にクリーンアップされることに注意してください。リダイレクトでは、完了時に「$ haconf_out」を削除するように注意する必要があります。

ほどエレガントではありませんpipefailが、この機能が手に入らない場合の正当な代替手段です。


1
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$

0

(少なくともbashをset -e使用)1つと組み合わせると、サブシェルを使用してpipefailを明示的にエミュレートし、パイプエラーで終了できます

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

foo何らかの理由で失敗した場合-プログラムの残りの部分は実行されず、スクリプトは対応するエラーコードで終了します。(これはfoo、失敗の理由を理解するのに十分な独自のエラーを出力することを前提としています)


-1

編集:この答えは間違っていますが、興味深いので、今後の参考のために残しておきます。


!コマンドにを 追加すると、戻りコードが反転します。

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command     # bash: bogus_command: command not found
echo $?                # 127

! ls | bogus_command   # bash: bogus_command: command not found
echo $?                # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #

1
これは無関係だと思います。あなたの例では、終了コードを知りたいのですが、終了コードをls反転させるのではなくbogus_command
Michael Mrozek

2
その答えを引き出すことをお勧めします。
-maxschlepzig

3
まあ、私はばかだと思われます。OPが望んでいたことを実行する前に、実際にスクリプトでこれを使用しました。重要なことには使用しなかった良いこと
ファルマーリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.