プロセス置換を使用するときに終了コードを正しくキャプチャしてエラーを処理する方法は?

プロセス置換を使用するときに終了コードを正しくキャプチャしてエラーを処理する方法は?

以下を使用してファイル名を配列に解析するスクリプトがあります。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=$?それを使用して進行方法を決定します。上記のプロセス置換では機能しないようです。

この状況で正しい手順は何ですか?戻りコードをキャプチャする方法は?交換プロセス中に問題が発生したかどうかを確認するためのより適切な方法はありますか?

答え1

標準出力に戻り値を反映することで、サブシェルプロセスの戻り値を簡単に取得できます。プロセスの交換も同様です。

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

私が実行すると、最後の行は -(または\0適切に区切られた部分)返品の状態はですfindreadEOFを受け取ると1を返します。したがって、$return設定される唯一の時間は、$FILE情報の最後のビットを読み取るときです。

私は余分な行をprintf追加するのを避けました。これは、NULで区切られていない通常の実行でも、先ほど読み取ったデータが1行で終わらないとゼロ以外の値を返すため、\n重要です。したがって、最後の行がewlineで終わらない場合は、変数で読み取られた最後の値が戻り値になります。read\0\n\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=...セクションは必ずしも必要ではありません。概念を説明するためにのみ使用されます。

答え2

使う共同プロセスcoproc組み込み関数を使用すると、子プロセスを開始し、出力を読み取り、終了ステータスを確認できます。

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

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

現在のPIDは$LS_PID呼び出される前に設定解除されるため、別の変数にコピーする必要があります。waitバラよりBash は coproc を待つ前に *_PID 変数を設定解除します。もっと学ぶ。

答え3

プロセス交換のプロセスは非同期です。シェルは、そのプロセスを開始してから終了時点を検出する方法を提供しません。したがって、終了ステータスを取得できません。

終了状態をファイルに書き込むことができますが、ファイルがいつ書き込まれたのかわからないため、これはしばしば不器用な操作です。ここで、ファイルはループが終了した直後に作成されるので、待つのが合理的です。

… < <(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などのより強力な言語を使用する必要があるようです。

答え4

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で処理するのに最も苦痛な方法ではありません。

関連情報