パイプを介して接続された2つのプロセスfoo
があります。bar
$ foo | bar
bar
常に0で終了します。終了コードに興味がありますfoo
。それを得る方法はありますか?
答え1
bash
また、zsh
シェルが実行した最後のパイプラインの各要素(コマンド)の終了状態を保持する配列変数があります。
を使用すると、配列がbash
呼び出されPIPESTATUS
(大文字と小文字が区別されます)、配列インデックスは0から始まります。
$ 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
上記のコードを or で実行すると、and の 1 つだけをzsh
設定すると同じ結果が得られますretval_bash
。retval_zsh
もう一つは空です。これにより、関数は次のようになりますreturn $retval_bash $retval_zsh
(引用符がないことに注意してください!)。
答え2
これを達成する3つの一般的な方法があります。
パイプ故障
最初の方法はpipefail
オプション(ksh
、、zsh
またはbash
)を設定することです。これが最も簡単です。デフォルトでは、終了ステータスを$?
最後のプログラムの終了コードに設定し、ゼロ以外の値(またはすべて正常に終了した場合は0)で終了します。
$ 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
答え3
このソリューションは、bash固有の機能や一時ファイルを使用せずに機能します。ボーナス:最終終了ステータスは、実際にはファイルの一部の文字列ではなく終了ステータスです。
状態:
someprog | filter
希望の終了状態someprog
と出力filter
。
これが私の解決策です。
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
この構成の結果は、filter
コンストラクタの stdout の stdout であり、someprog
コンストラクタの終了状態の終了状態です。
{...}
この構造は、サブシェルではなく単純なコマンドのグループ化にも機能します(...)
。サブシェルには、ここでは不要なパフォーマンスコストを含むいくつかの意味があります。詳細についてはnice 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が継承されることを意味します。ファイル記述子3に書き込むと、someprog
これが終了状態になります。実際の終了状態はread
一度だけ読み取られるので無視されます。
ファイル記述子3または4に書き込む可能性が懸念される場合は、呼び出すsomeprog
前にファイル記述子を閉じることをお勧めしますsomeprog
。
(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
before は実行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
下から上へ:
- stdoutにリダイレクトされたファイル記述子4を使用してサブシェルを作成します。これは、サブシェルのファイル記述子4に印刷されたすべての内容が、構成全体の標準出力で終了することを意味します。
- パイプラインを作成し、
#part3
左()と右()のコマンドを実行します。また、パイプラインの最後のコマンドは、stdinの文字列が構成全体の終了状態になることを意味します。#part2
exit $xs
- stdoutにリダイレクトされたファイル記述子3を使用してサブシェルを作成します。これは、このサブシェルのファイル記述子3に印刷されたすべての内容が最終的に
#part2
全体構成の終了状態になることを意味します。 - パイプを作成し、左(および
#part5
)#part6
と右()のコマンドを実行します。filter >&4
出力はfilter
ファイル記述子4にリダイレクトされます。#part1
ファイル記述子4のstdoutにリダイレクトされます。これは、出力がfilter
全体構成の標準出力であることを意味します。 - 終了状態は
#part6
ファイル記述子3に印刷される。#part3
ファイル記述子3からリダイレクトします#part2
。これは、終了状態が#part6
全体構成の最終終了状態になることを意味します。 someprog
処刑される。終了ステータスが受信されました#part5
。標準出力はパイプ入力として得られ、#part4
に渡されますfilter
。で説明されているように、willの出力はfilter
順番に標準出力に移動します。#part4
答え4
可能であれば終了コード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
があります。私の考えでは、最大512バイトまで書き込むすべてのユニークパイプはアトミックなので、タグ文字列が507バイト未満の場合と部分が混在しません。bar:Y foo:X
bar
foo:$?
bar:$?
キャプチャされた出力が必要な場合はbar
難しくなります。終了コードが示すように見える行を含むようにneverの出力をソートすることで、上記の手法を組み合わせることができますが、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
、これは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")