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

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

私は、次のメソッドを使用してファイル名を配列に解析するスクリプトを持っています。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場合によっては区切られたセクション)findは の戻りステータスになります。readは EOF を取得すると 1 を返します。したがって、$returnに設定されるのは$FILE、読み込まれた情報の最後のビットのみです。

私はprintf余分な\newline を追加しないようにしています。これは、NULreadで区切らない通常の実行であっても、\0読み込んだデータが ewline で終わらない場合は 0 以外の値を返すため重要です\n。したがって、最後の行が ewline で終わらない場合は\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ゼロ以外のステータス コードで終了します。

$LS_PIDが呼び出される前にPIDが未設定になるため、現在はPIDを別の変数にコピーする必要がありますwaitbash は、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)

別の方法としては、名前付きパイプとバックグラウンド プロセス ( waitfor で使用可能) を使用する方法があります。

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 で処理する最も簡単な方法かもしれません。

関連情報