回答:
あなたが使用している場合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_bash
とretval_zsh
設定されます。もう一方は空白になります。これにより、関数を終了できますreturn $retval_bash $retval_zsh
(引用符がないことに注意してください!)。
pipestatus
zshで。残念ながら、他のシェルにはこの機能がありません。
echo "$pipestatus[1]" "$pipestatus[2]"
です。
if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
これを行うには、3つの一般的な方法があります。
最初の方法は、pipefail
オプション(ksh
、zsh
またはbash
)を設定することです。これは最も単純で、基本的には、終了ステータス$?
を最後のプログラムの終了コードに設定して、ゼロ以外(またはすべてが正常に終了した場合はゼロ)に終了します。
$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1
Bashには、最後のパイプラインのすべてのプログラムの終了ステータスを含む$PIPESTATUS
($pipestatus
in 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
ksh
ていませんが、マンページを少し見ただけではサポートされていません$PIPESTATUS
。pipefail
ただし、このオプションはサポートされています。
LOG=$(failed_command | successful_command)
このソリューションは、bash固有の機能または一時ファイルを使用せずに機能します。ボーナス:最終的に、終了ステータスは実際には終了ステータスであり、ファイル内の文字列ではありません。
状況:
someprog | filter
からの終了ステータスとからsomeprog
の出力が必要filter
です。
ここに私の解決策があります:
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
この構成の結果は、構成のstdoutからのfilter
stdoutであり、構成の終了ステータス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の文法では、中括弧にスペースとセミコロンが必要なので、構造がより広くなります。
このテキストの残りの部分では、サブシェルバリアントを使用します。
例someprog
とfilter
:
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
下から上へ:
#part3
)および右(#part2
)のコマンドが実行されます。exit $xs
また、パイプの最後のコマンドであり、これは、stdinからの文字列が構造全体の終了ステータスになることを意味します。#part2
的には構造全体の終了ステータスになることを意味します。#part5
および#part6
)および右(filter >&4
)のコマンドが実行されます。の出力は、ファイル記述子4にリダイレクトされますfilter
。ファイル記述子4では#part1
、stdoutにリダイレクトされました。これは、の出力がfilter
構造全体の標準出力であることを意味します。#part6
で記述子3をファイルに出力された#part3
ファイルディスクリプタ3はにリダイレクトされました#part2
。これは、からの#part6
終了ステータスが、構造全体の最終的な終了ステータスになることを意味します。someprog
実行されます。終了ステータスはに取り込まれ#part5
ます。stdoutはパイプに取り込まれ、に#part4
転送されfilter
ます。からの出力filter
は、次に説明するように、stdoutに到達します。#part4
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
に簡素化しsomeprog 3>&- 4>&-
ます。
{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
正確にあなたが尋ねたものではありませんが、使用することができます
#!/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
set -o pipefail
たとえば、誰かがを介してスクリプトを実行する場合に備えて、スクリプト内の堅牢性を高めてくださいbash foo.sh
。
-o pipefail
POSIXにはないことに注意してください。
#!/bin/bash -o pipefail
ます。エラー:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
、最初の行を超える行のスペースを解析しないため、これ/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
可能であれば、終了コードをにフィードfoo
しbar
ます。たとえば、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")
パイプラインから開始:
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
改行がスペースに強制されるため、それらなしでは区別できません。
だから私はレスマナのような答えを提供したかったのですが、おそらく私のほうが少しシンプルで少し有利な純ボーンシェルソリューションだと思います:
# 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
。
上記のlesmanaのソリューションは、{ .. }
代わりにを使用して、ネストされたサブプロセスを開始するオーバーヘッドなしで実行することもできます(グループ化されたコマンドのこの形式は常にセミコロンで終了する必要があることを思い出してください)。このようなもの:
{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1
ダッシュバージョン0.5.5とbashバージョン3.2.25および4.2.42でこの構造を確認したため、一部のシェルが{ .. }
グループ化をサポートしていない場合でも、POSIXに準拠しています。
set -o pipefail
、kshまたは任意の数のスプリンクルwait
コマンドを使用しても、AT&T Ksh(93s +、93u +)またはzsh(4.3.9、5.2)で動作させることはできません。サブシェルの使用に固執するとうまくいくように、少なくとも部分的にはkshの解析の問題かもしれないと思いますが、ksh if
のサブシェルバリアントを選択しても、他の人のために複合コマンドを残すと、失敗します。
これは移植性があります。つまり、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))
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
。@Johan:Bashの方が簡単だと思いますが、状況によっては、Bashを回避する方法を知っておく価値があります。
以下は、一般的なソリューションのいずれかを使用できない場合に備えて、@ Patrikの回答に対するアドオンとして使用されます。
この回答では、次のことを前提としています。
$PIPESTATUS
たりset -o pipefail
追加の仮定。あなたはすべてを取り除くことができますが、これはレシピを過剰に破壊するため、ここでは説明しません:
- 知りたいのは、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
wait
以下のために必要とされているksh
ので、ksh
他にはフィニッシュにすべてのパイプコマンドを待ちません。ただし、バックグラウンドタスクが存在する場合、不要なsideffectsがあることに注意してください。そのため、デフォルトでコメントアウトしました。待機に支障がない場合は、コメントしてください。read
はを返しfalse
、true
エラーを示しますこれは、単一のコマンドのプラグインの置換として使用でき、以下のみが必要です。
/proc/fd/N
バグ:
このスクリプトには、/tmp
スペースが不足した場合のバグがあります。この人為的なケースに対する保護も必要な場合は、次のように行うことができますが、これには、パイプ内のコマンドの数0
に000
応じて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でテストされたsh
、bash
、ksh
、ash
、sash
とさえcsh
次の「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
が、この機能が手に入らない場合の正当な代替手段です。
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
$
編集:この答えは間違っていますが、興味深いので、今後の参考のために残しておきます。
!
コマンドにを
追加すると、戻りコードが反転します。
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.
# =========================================================== #
ls
反転させるのではなくbogus_command