Pipefailと同様にバックティックが失敗した場合にbashを終了するにはどうすればよいですか?

Pipefailと同様にバックティックが失敗した場合にbashを終了するにはどうすればよいですか?

したがって、私はエラーが発生しないようにbashスクリプトをできるだけ強化することが好きです(Python / Rubyなどの言語への委任が不可能な場合)。

これらの精神には、次の内容を含むstrict.shがあります。

set -e
set -u
set -o pipefail

別のスクリプトにソースを追加します。ただし、パイプラインエラーが発生している間:

false | echo it kept going | true

受信されません:

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

出力は次のとおりです

出力は ''

False はゼロ以外の状態を返し、標準出力を返しません。パイプラインでは失敗しますが、このエラーはキャッチされません。実際には、後で使用するために変数に格納された計算であり、値が空白に設定されている場合、後で問題が発生する可能性があります。

それでは、bashがバックティック内でゼロ以外の戻りコードを終了するのに十分な理由で処理するようにする方法はありますか?

答え1

使用される正確な言語単一のUNIX仕様説明する意味set -e例:

このオプションがオンの場合簡単なコマンド記載された理由により失敗しました。シェルエラーの結果または、終了ステータス値> 0を返し、[条件付きまたは否定的なコマンド]でない場合、シェルはすぐに終了する必要があります。

そのようなコマンドサブシェル。実用的な観点からサブシェルができることは終了し、親シェルにゼロ以外の状態を返すだけです。親シェルが順次終了するかどうかは、ゼロ以外の状態が親シェルで失敗する単純なコマンドに変換されるかどうかによって異なります。

あなたが持っているのは問題がある状況です:コマンドの置き換え。この状態は無視されるため、親シェルは終了しません。 〜のようにあなたは見つけました、終了状態について考える1つの方法は、単純なコマンド置換を使用することです。仕事:その後割り当ての終了状態は、割り当ての最後のコマンド置換の終了状態です。

最後の代替状態のみが考慮されるため、単一のコマンド置換がある場合にのみ期待どおりに機能します。たとえば、次のコマンドは成功しました(標準と私が見たすべての実装によると)。

a=$(false)$(echo foo)

注意が必要な別の状況は次のとおりです。明示的なサブシェル(somecommand)。上記の説明では、サブシェルがゼロ以外の状態を返す可能性がありますが、これは親シェルでは単純なコマンドではないため、親シェルを続行する必要があります。実際、私が知っているすべてのシェルはこの時点で親を返します。これは、現在のディレクトリの変更などのタスクをローカルに保持するために括弧を使用するなど、多くの場合に便利ですが、サブシェルで閉じられた場合、またはサブシェルが次のようにゼロ以外の状態(cd /some/dir && somecommand)を返す場合、仕様に違反しますset -e。たとえば、!trueコマンドに使用されます。たとえば、This should be displayed次の例では、ash、bash、pdksh、ksh93、および zsh はすべて表示されずに終了します。

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

set -eしかし、実際には単純なコマンドは失敗しません!

3番目の問題のケースはマイナーな要素ではありません。管路。実際、すべてのシェルは、最後のパイプ要素を除くすべての要素の失敗を無視し、最後のパイプ要素に関連する2つの動作のうちの1つを表します。

  • ATT kshとzshは、通常どおり親シェルでパイプラインの最後の要素を実行します。パイプラインの最後の要素で単純なコマンドが失敗すると、コマンドを実行したシェル(親シェル)が終了します。 。
  • 他のシェルは、パイプの最後の要素がゼロ以外の状態を返す場合、終了して同様の動作を実行します。

以前と同様に、set -eパイプの最後の要素で否定を閉じるか使用すると、ATT ksh および zsh 以外のシェルが終了しないように、ゼロ以外の状態が返されます。

バッシュpipefailset -eオプションを使用すると、パイプの要素がゼロ以外の状態を返すと、パイプはすぐに終了します。

set -e問題をより複雑にするために、bashはコマンド置換で閉じられていますが、いいえbashがPOSIXモードにない場合(つまり、bashが起動したときに環境に設定されるか、bashが呼び出される)通常のサブシェル(つまり内部``または$()内部にはありません)。この場合、現在の設定は親から取得されます。シェル継承された呼び出し時間(コマンドの代替またはサブシェルのいずれか)。()set -o posixPOSIXLY_CORRECTshe

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

答え2

(解決策を見つけた私自身の質問に答えます)1つの解決策は常に中間変数に割り当てることです。これは戻りコード($?)を設定します。

だから

ABC=`exit 1`
echo $?

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

echo `exit 1`
echo $?

0空行の後に出力されます。 echoの戻りコード(またはバックティック出力として実行される他のコマンド)は、ゼロ戻りコードを置き換えます。

私はまだ中間変数を必要としない解決策で開いていますが、これは私にいくつかの助けを与えました。

答え3

OPが自分の答えで指摘したように、サブコマンドの出力を変数に割り当てても問題は解決しません$?

しかし、まだ混乱する可能性がある極端なケースは、偽の否定(つまり、コマンドは失敗しましたがエラーは表示されません)、つまり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

答え4

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

function testcase()
{
    local MYRESULT

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

    return 0
}

出力:

$ testcase
False returned false!
$ 

関連情報