bash:プロセス置換でエラーを伝播する方法は?


19

シェルスクリプトを使用して実行されたコマンドが失敗するたびにシェルスクリプトが失敗するようにします。

通常、私はそれを以下で行います:

set -e
set -o pipefail

(通常、私set -uも追加します)

問題は、上記のいずれもプロセス置換では機能しないことです。このコードは「ok」を出力し、リターンコード= 0で終了しますが、失敗させたいと思います。

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

「pipefail」に相当するものがありますが、プロセスの代替はありますか?コマンドの出力をファイルであるとしてコマンドに渡す他の方法がありますが、これらのプログラムのいずれかが失敗するたびにエラーが発生しますか?

貧乏人の解決策は、これらのコマンドがstderrに書き込むかどうかを検出することです(ただし、一部のコマンドは成功したシナリオでstderrに書き込みます)。

posix準拠のもう1つのソリューションは、名前付きパイプを使用することですが、コンパイル済みコードからオンザフライで構築されたonelinersとして、それらのコマンドを使用するプロセス置換を実行する必要があり、名前付きパイプを作成すると物事が複雑になります(余分なコマンド、削除など)


名前付きパイプを削除するためにエラーをトラップする必要はありません。実際、それはまったく良い方法ではありません。検討してくださいmkfifo pipe; { rm pipe; cat <file; } >pipe。読者が開くまでそのコマンドがハングしますpipeそれがないシェルですのでopen()ので、すぐに読者があるとしてpipe用FSリンクがpipeあるrmD」と、その後catのコピーがそのパイプのシェルの記述に出てINFILE。あなたは、プロセス、サブのうち、エラーを伝播する場合と、とにかく、やる: <( ! : || kill -2 "$$")
mikeserv

名前付きパイプの削除に関するヒントをありがとう。残念ながら、$$プロセス置換を使用するコマンドは「非シェル」コード(python)から生成されるコマンドパイプライン内で実行されるため、コマンド置換は実行されません。おそらく、Pythonでサブプロセスを作成し、プログラムでパイプする必要があります。
フアンレオン

を使用しますkill -2 0
mikeserv

残念ながら、「kill -2 0」はPythonを(シグナル)殺します。マルチスレッドアプリケーション内でビジネスロジックを実行するためのシグナルハンドラを
記述

シグナルを処理したくない場合、なぜそれらを受信しようとしていますか?とにかく、名前付きパイプが最終的に最も簡単なソリューションになると思います。それを正しく行うには、独自のフレームワークをレイアウトし、独自のディスパッチャを設定する必要があるからです。そのハードルを乗り越えると、すべてが一緒になります。
mikeserv

回答:


6

たとえば、その問題を回避するには、次のようにします。

cat <(false || kill $$) <(echo ok)
other_command

SIGTERM2番目のコマンドを実行する前に、スクリプトのサブシェルはd other_commandです()。echo okコマンドは「時々 」が実行されています。問題は、プロセス置換が非同期であるということです。保証はありませんkill $$コマンドが実行されるまたは後にecho okコマンドが。オペレーティングシステムのスケジューリングの問題です。

次のようなbashスクリプトを検討してください。

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

そのスクリプトの出力は次のとおりです。

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

または:

$ ./script
Terminated
$ pre
post

$ echo $?
143

それを試してみることができ、数回試してみると、出力に2つの異なる順序が表示されます。最初のスクリプトでは、他の2つのechoコマンドがファイル記述子に書き込む前にスクリプトが終了しました。2番目falsekillコマンドでは、コマンドはおそらくechoコマンドの後にスケジュールされました。

または、より正確に言うと、シェルプロセスにシグナルを送信signal()するkillユーティリティのシステムコールは、エコーシスコールSIGTERMよりも遅くまたは早くスケジュールされました(または配信されました)write()

ただし、スクリプトは停止し、終了コードは0ではありません。したがって、問題を解決する必要があります。

別の解決策は、もちろん、これに名前付きパイプを使用することです。ただし、名前付きパイプまたは上記の回避策を実装することがどれほど複雑になるかは、スクリプトによって異なります。

参照:


2

記録のために、そして答えとコメントが良くて有益だったとしても、私は少し違うものの実装を終了しました(質問で言及しなかった親プロセスでのシグナルの受信についていくつかの制限がありました)

基本的に、私はこのようなことをやめました:

command <(subcomand 2>error_file && rm error_file) <(....) ...

次に、エラーファイルを確認します。存在する場合、どのサブコマンドが失敗したかがわかります(そして、error_fileの内容が役立つ場合があります)。私が最初に欲しかったより冗長でハックがありますが、1行のbashコマンドで名前付きパイプを作成するよりも面倒ではありません。


通常、あなたに合った方法が最善の方法です。特に戻ってきて答えてくれてありがとう-セルフィーは私のお気に入りです。
mikeserv

2

この例は、とともに使用killする方法を示していますtrap

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

ただしkill、サブプロセスから親プロセスに戻りコードを渡すことはできません。


受け入れ答えに、この変化のようなI
sehe

1

サポートしていないPOSIXシェルで/ 実装$PIPESTATUS$pipestatusするのと同様の方法で、コマンドをパイプ経由で渡すことでコマンドの終了ステータスを取得できます。

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

与えるもの:

ok
cat_code=0
false_code=1
echo_code=0

またはpipefail、それをサポートしていないシェルの場合と同様に、プロセス置換を手動で使用および実装できます。

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.