web-dev-qa-db-ja.com

bashオプションを確認する

関数があり、内部でpipefailオプションを使用したいと思います。しかし、単純にset -o pipefailを使用したくありません。スクリプトの別の部分がpipefailの設定を期待しない可能性があるためです。もちろん、後でset +o pipefailを実行することもできますが、pipefailが関数の外部で設定されている場合、予期しない動作が発生する可能性もあります。

もちろん、pipefailが設定されている場合、false | trueの終了コードをメジャーとして使用できますが、これは少し汚いようです。

設定されたbashオプションを確認するためのより一般的な(そしておそらく標準的な?)方法はありますか?

6
red_trumpet

$SHELLOPTS変数は、コロンで区切られたすべての設定オプションを保持します。例として、次のようになります。

braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor

特定のオプションを確認するには、次のようにします。

# check if pipefail is set, if not set it and remember this
if [[ ! "$SHELLOPTS" =~ "pipefail" ]]; then
    set -o pipefail;
    PIPEFAIL=0;
else
    PIPEFAIL=1;
fi

# do something here

# reset pipefail, if it was not set before
if [ "$PIPEFAIL" -eq 0 ]; then
    set +o pipefail;
fi
3
red_trumpet

bash-4.4以上では、以下を使用できます。

myfunction() {
  local -
  set -o someoption
  ...
}

いくつかのメモ:

  • それはashからコピーされます。 ashのアイデアは、$-(オプションのセットを格納する)をローカルにすることです。 ashで機能するのは、ashではlocalは変数の値や属性に影響を与えないためです(bashとは逆に、最初は変数を設定解除します) )。それでも、bashと同じようにashと同じように機能します。つまり、$-を設定解除したり、デフォルトの動作に戻したりすることはありません。
  • set -o optionで設定されたオプションではなく、shopt -s optionで設定されたオプションのみをカバーします(bashは2つのオプションセットを持つ唯一のシェルです!)
  • ローカル変数のように、それは静的ではなく動的スコープです。 $-の値は、関数から戻るときに復元されますが、新しい設定は、関数によって呼び出される他の関数またはソーススクリプトに影響を与えます。静的スコープが必要な場合は、ksh93に切り替えて以下を使用する必要があります。

    function myfunction {
      set -o myoption
      ...
      other_function
    }
    

    other_function構文で宣言されている限り、function other_function {は影響を受けません(Bourne other_function()...構文ではない)。

  • オプションはリセットされないため、関数は他のコードによって設定されたオプションの影響を受ける可能性があります。これを回避するには、代わりにzshを使用します。 zshlocal -に相当するものはset -o localoptionsですが、よく知られているエミュレーションモードを使用することもできます。ローカルに。例えば:

    myfunction() {
      emulate -L zsh
      set -o someoption
      ...
    }
    

    正気なzsh-defaultオプションのセット(いくつかの例外はありますが、ドキュメントを参照)から始め、その上にオプションを追加します。これはash/bashのような動的スコープでもありますが、次のように他の関数を呼び出すこともできます。

    emulate zsh -c 'other_function...'
    

    そのother_functionをVanilla zshオプションを設定して呼び出すため。

    zshには、最初からグローバルにオプションを変更しないようにすることができるいくつかの演算子もあります(_nullglob/dotglob(N)/(D) glob修飾子、大文字と小文字を区別しないglobbingの(#i) glob演算子など、${(s:x:)var}$IFS${~var}との混同を避けるために、パラメータ展開時にグロビングするrequest(他のBourne-ではnoglobが必要)シェルのようにavoid(!)it)...

7

関数内でシェルオプションを設定したいが、呼び出し元に影響を与えたくない場合は、2つの異なる方法で行います。

オプション1:サブシェル

関数をサブシェル内に置きます(中括弧の代わりに関数のステートメントを囲む括弧に注意してください):

function foo() (
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
)

次に、呼び出し元はpipefailを設定解除でき、影響を受けません。

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

または

オプション2:テストとリセット

必要に応じてオプションをテストおよびリセットできます(関数ステートメントを囲む中括弧に注意してください-サブシェルはありません)。

function foo() {
  shopt -q -o pipefail
  local resetpipefail=$?
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
  # only reset pipefail if needed
  [[ $resetpipefail -eq 1 ]] && set +o pipefail  
}

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

cuonglm への帽子のヒント shopt -q回答

3
Jeff Schaller

以下は、bash-oテストを使用して、pipefailシェルオプションが設定されているかどうかをテストします(これは[[ ... ]]で機能し、test -inコマンド、ただし[ ... ]は使用できません)。オプションが設定されていない場合は、オプションを設定し、関数が戻ったときに設定を解除するトラップをインストールします。

foo () {
    if [[ ! -o pipefail ]]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    # rest of function body
}

デバッグが含まれたコード:

foo () {
    if [[ ! -o pipefail ]]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    # rest of function body
    if [[ -o pipefail ]]; then
        echo 'pipefail is set (in function)'
    else
        echo 'pipefail is not set (in function)'
    fi
}

if [[ -o pipefail ]]; then
    echo 'pipefail is set'
else
    echo 'pipefail is not set'
fi

foo

if [[ -o pipefail ]]; then
    echo 'pipefail is set'
else
    echo 'pipefail is not set'
fi

それを実行する:

$ bash script.sh
pipefail is not set
pipefail is set (in function)
pipefail is not set

$ bash -o pipefail script.sh
pipefail is set
pipefail is set (in function)
pipefail is set
2
Kusalananda

RETURNトラップを使用する

RETURNトラップで指定されたコマンドは、シェル関数が戻った後に実行が再開する前に実行されます...

-pオプション[to shopt]は、出力を入力として再利用できる形式で表示します。

https://www.gnu.org/software/bash/manual/bash.html

foo() {
  trap "$(shopt -p -o pipefail)" RETURN
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
}

テスト

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

次の関数テンプレートは、setオプションとshoptオプションの両方が、関数が戻ったときに元の値に復元されるようにする一般的なケースを処理します。

foo() {
  trap "$(shopt -p -o)$(shopt -p)" RETURN
  # ...
}
1
Robin A. Meade