プロセス置換を使用するときに、終了コードをキャプチャする/エラーを正しく処理するにはどうすればよいですか?


13

SOのQ&Aから取得した次のメソッドを使用して、ファイル名を解析して配列にするスクリプトがあります。

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

これはうまく機能し、あらゆる種類のファイル名のバリエーションを完全に処理します。ただし、存在しないファイルをスクリプトに渡すこともあります。例:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

通常の状況では、スクリプトで終了コードをキャプチャし、次のようなRET=$?方法で終了コードを取得します。これは、上記のプロセス置換では機能しないようです。

このような場合の正しい手順は何ですか?リターンコードをキャプチャするにはどうすればよいですか?代替プロセスで何か問題が発生したかどうかを判断する他のより適切な方法はありますか?

回答:


5

戻り値を標準出力にエコーすることで、サブシェルプロセスから簡単に戻り値を取得できます。プロセス置換についても同じことが言えます。

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

それを実行すると、最後の行(または\0場合によっては区切りセクション)findの戻りステータスになります。readEOFを取得すると1を返します。したがって、$return設定されるの$FILEは、読み込まれた情報の最後のビットだけです。

printf余分な\newline を追加しないようにするために使用します-これは重要です。なぜなら、read定期的に実行された-NULで区切られていないものでも\0-読み込んだばかりのデータが終了しない場合に0以外を返すからです。\newline。したがって、最後の行が最後の行で終わらない場合、\n読み取り変数の最後の値が戻り値になります。

上記のコマンドを実行してから:

echo "$return"

出力

0

そして、プロセス置換部分を変更すると...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

出力

1

より簡単なデモ:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

出力

pipe

そして実際、あなたが望むリターンがプロセス置換内からstdoutに最後に書き込むものである限り、またはこの方法で読み取るサブシェルプロセスは、それ$FILEがいつでもあなたが望むリターンステータスになりますである。そのため、この|| ! return=...部分は厳密に必要ではありません-概念のみを示すために使用されます。


5

プロセス置換のプロセスは非同期です:シェルはそれらを起動し、その後、それらがいつ死ぬかを検出する方法を提供しません。そのため、終了ステータスを取得することはできません。

終了ステータスをファイルに書き込むことはできますが、ファイルがいつ書き込まれるのかわからないため、これは一般的に不格好です。ここでは、ファイルはループの終了後すぐに書き込まれるため、待機するのが妥当です。

 < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

別のアプローチは、名前付きパイプとバックグラウンドプロセス(使用可能)を使用することwaitです。

mkfifo find_pipe
find  >find_pipe &
find_pid=$!
 <find_pipe
wait $find_pid
find_status=$?

どちらのアプローチも適切でない場合は、Perl、Python、Rubyなどのより能力の高い言語に向かう必要があると思います。


この答えをありがとう。説明した方法は正常に機能しますが、予想よりも少し複雑であることを認めなければなりません。私の場合、質問に示されているループの前にすべての引数を反復処理し、そのうちの1つがファイルまたはフォルダーでない場合はエラーを出力するループに落ち着きました。これは、代替プロセスで発生する可能性のある他のタイプのエラーを処理しませんが、この特定の場合には十分です。このような状況でより洗練されたエラー処理方法が必要になった場合は、必ず答えに戻ります。
グルタニメート14年

2

コプロセスを使用します。coprocビルトインを使用すると、サブプロセスを開始し、その出力を読み取り、終了ステータスを確認できます。

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

ディレクトリが存在しない場合、waitゼロ以外のステータスコードで終了します。

呼び出される$LS_PID前に設定解除されるため、現在、PIDを別の変数にコピーする必要がありますwait。詳細については、coproc待機する前に、Bashが* _PID変数を設定解除するをご覧ください。


1
<& "$ LS"とread -u $ LSをいつ使用するのか興味があります。-ありがとう
ブライアンクリスマン

1
@BrianChrismanこの場合、おそらく決してありません。read -u同様に動作するはずです。この例は汎用的なものであり、コプロセスの出力を別のコマンドにパイプする方法を示しています。
Feuermurmel

1

1つのアプローチは次のとおりです。

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

アイデアは、コマンドの完了後にランダムトークンとともに終了ステータスをエコーし​​、bash正規表現を使用して終了ステータスを検索および抽出することです。トークンは、出力で検索する一意の文字列を作成するために使用されます。

それはおそらく一般的なプログラミングの意味でそれを行うための最良の方法ではありませんが、bashでそれを処理するための最も痛みのない方法かもしれません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.