web-dev-qa-db-ja.com

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

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=$?のような終了コードをキャプチャさせ、それを使用して続行する方法を決定します。これは、上記のプロセス置換では機能しないようです。

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

13
Glutanimate

Stdoutにリターンをエコーすることで、サブシェル化されたプロセスからリターンを簡単に取得できます。同じことがプロセス置換にも当てはまります。

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を追加しないようにしています。これは、readでも定期的に実行されるため重要です-\0 NULで区切られていないもの-読み込んだばかりのデータが\newlineで終わっていない場合、0以外を返します。したがって、最後の行が\newlineで終わっていない場合、readin変数の最後の値が戻り値になります。

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

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
mikeserv

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

終了ステータスをファイルに書き込むことはできますが、ファイルがいつ書き込まれたかわからないため、これは一般に扱いにくいものです。ここでは、ファイルはループの終了直後に書き込まれるため、それを待つのが妥当です。

… < <(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などのより高性能な言語に向かう必要があると思います。

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

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

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

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

2
Feuermurmel

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で処理するのに最も苦痛の少ない方法かもしれません。

1
orev