pipefailと同様の方法でバックティックの失敗時にbashを終了させるにはどうすればよいですか?


55

したがって、エラーがキャッチされないように、できる限り(およびPython / Rubyのような言語に委任できない場合)にbashスクリプトを強化するのが好きです。

その流れには、次のようなものを含むstrict.shがあります。

set -e
set -u
set -o pipefail

そして、他のスクリプトでそれを入手します。ただし、pipefailは次のことを認識します。

false | echo it kept going | true

それは拾いません:

echo The output is '`false; echo something else`' 

出力は次のようになります

出力は ''

Falseは、ゼロ以外のステータスとno-stdoutを返します。パイプでは失敗しますが、ここではエラーはキャッチされません。これが実際に後で変数に保存される計算であり、値が空白に設定されている場合、これはその後の問題を引き起こす可能性があります。

だから-バックティック内のゼロ以外のリターンコードを終了するのに十分な理由としてbashを取得する方法はありますか?

回答:


51

Single UNIX仕様で意味set -eを記述するために使用される正確な言語は次のとおりです。

このオプションがオンの場合、単純なコマンド「シェルエラーの結果」にリストされている理由のいずれかで失敗するか、終了ステータス値> 0を返し、[条件付きコマンドまたは否定コマンド]ではない場合、シェルは直ちに終了します。

そのようなコマンドがサブシェルで発生したときに何が起こるかについてはあいまいさがあります。実用的な観点から見ると、サブシェルでできることは、終了してゼロ以外のステータスを親シェルに返すことだけです。親シェルが順に終了するかどうかは、この非ゼロのステータスが親シェルで失敗する単純なコマンドに変換されるかどうかに依存します。

このような問題のあるケースの1つは、コマンド置換からのゼロ以外の戻りステータスです。このステータスは無視されるため、親シェルは終了しません。すでに発見した、考慮に終了ステータスを取るための方法は、単純な中でコマンド置換を使用することです割り当て:その後、割り当ての終了ステータスが割り当て(S)の最後のコマンド置換の終了ステータスです

最後の置換のステータスのみが考慮されるため、単一のコマンド置換がある場合にのみ、これが意図したとおりに実行されることに注意してください。たとえば、次のコマンドは成功します(標準と私が見たすべての実装の両方):

a=$(false)$(echo foo)

注目すべき別のケースは、明示的なサブシェルです(somecommand)。上記の解釈によると、サブシェルはゼロ以外のステータスを返す場合がありますが、これは親シェルの単純なコマンドではないため、親シェルは続行する必要があります。実際、私が知っているすべてのシェルは、この時点で親を返します。これは(cd /some/dir && somecommand)、現在のディレクトリの変更などの操作をローカルで保持するために括弧を使用する場合など、多くの場合に役立ちますがset -e、サブシェルでオフになっている場合、またはサブシェルがゼロ以外のステータスを返す場合、仕様に違反します!真のコマンドで使用するなど、終了しません。たとえば、次の例では、ash、bash、pdksh、ksh93、およびzshはすべて表示さfooれずに終了します。

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

まだset -e有効な間、単純なコマンドは失敗しませんでした!

3番目の問題は、重要なパイプラインの要素です。実際には、すべてのシェルは、最後以外のパイプラインの要素の障害を無視し、最後のパイプライン要素に関する次の2つの動作のいずれかを示します。

  • 親シェルでパイプラインの最後の要素を実行するATT kshおよびzshは、通常どおりビジネスを行います。パイプラインの最後の要素で単純なコマンドが失敗した場合、そのコマンドを実行するシェルはたまたま親シェルであり、終了します。
  • 他のシェルは、パイプラインの最後の要素がゼロ以外のステータスを返す場合に終了することで動作を近似します。

前と同様に、set -eパイプラインの最後の要素で無効にするか否定を使用すると、シェルを終了しないようにゼロ以外のステータスを返します。ATT kshおよびzsh以外のシェルは終了します。

Bashのpipefailオプションを使用するとset -e、その要素のいずれかがゼロ以外のステータスを返した場合、パイプラインがすぐに終了します。

さらに複雑なset -eことに、POSIXモード(set -o posixまたはPOSIXLY_CORRECTbashの開始時に環境内にある)でない限り、bashはサブシェルでオフになることに注意してください。

これはすべて、POSIX仕様が残念ながら-eオプションの指定で不十分な仕事をしていることを示しています。幸いなことに、既存のシェルの動作はほとんど一貫しています。


これをありがとう。私が経験したことは、bashの新しいバージョンでキャッチされたいくつかのエラーは、set -eを使用した以前のバージョンでは無視されたことでした。ここでの私の意図は、未処理のエラーの戻り/失敗条件によってスクリプトが終了する程度までスクリプトを強化することです。これは、ガベージ出力ファイルを生成することが知られているレガシーシステムで、間違ったenvで30分間の混乱の後、「0」の幸せな終了コードを出力します。 stderrにあり、一部はstdoutにあり、一部は/ dev / null'dです)、わかりません。
ダニーステープル

「たとえば、次の例では、ash、bash、pdksh、ksh93、およびzshのすべてがfooを表示せずに終了します」:BusyBox ashはそのように動作せず、仕様に従って出力を表示します。(BusyBox 1.19.4でテスト済み。)でも終了しませんset -e; (cd /nonexisting)
dubiousjim

27

(私は解決策を見つけたので自分自身に答えます)1つの解決策はこれを常に中間変数に割り当てることです。これにより、リターンコード($?)が設定されます。

そう

ABC=`exit 1`
echo $?

ただし、出力されます1(またはset -e存在する場合は終了します)。

echo `exit 1`
echo $?

0空白行の後に出力されます。エコーの戻りコード(またはバックティック出力で実行された他のコマンド)は、0の戻りコードを置き換えます。

私はまだ中間変数を必要としないソリューションを受け入れていますが、これは私に道を開きます。


23

OPが彼自身の答えで指摘したように、サブコマンドの出力を変数に割り当てると問題が解決します。$?無傷残っています。

ただし、エッジケースの1つでは、偽陰性(コマンドは失敗するがエラーはバブルアップしない)、local変数宣言で困惑する可能性があります。

local myvar=$(subcommand)、常に返します0

bash(1) これを指摘します:

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

簡単なテストケースを次に示します。

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

出力:

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1

ありがとう、私VAR=...との間の矛盾とlocal VAR=...本当に混乱した!
ジョニー

13

他の人が言ったように、local常に0を返します。解決策は、最初に変数を宣言することです:

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

出力:

$ testcase
False returned false!
$ 

4

コマンド置換の失敗で終了するには、次の-eようにサブシェルで明示的に設定できます。

set -e
x=$(set -e; false; true)
echo "this will never be shown"

1

興味深い点!

私はset -e(私は好むtrap ... ERR)の友達ではないので、私はそれを偶然見つけたことがtrap ... ERRありませんが、すでにそれをテストしました:また$(...)、内部のエラーをキャッチしないでください

問題は、(しばしば)ここでサブシェルが呼び出され、-e明示的に現在のシェルを意味することだと思います。

この時点で頭に浮かんだ他のソリューションは、readを使用することです。

 ls -l ghost_under_bed | read name

これはスローさERR-e、シェルで終了します。唯一の問題:これは、1行の出力を持つコマンド(または、行を結合する何かを介してパイプする)に対してのみ機能します。


1
読み取りのトリックについてはわかりません。それを試みると、変数がバインドされなくなります。これは、パイプの反対側が事実上サブシェルであり、名前が利用できないためだと思う。
ダニーステープル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.