-e と shopt -s Heritage_errexit が設定されている場合でも、入れ子になったコマンドの置換は失敗時にスクリプトを停止しません。

-e と shopt -s Heritage_errexit が設定されている場合でも、入れ子になったコマンドの置換は失敗時にスクリプトを停止しません。

次のスクリプトがあるとしますsandbox.sh

(これは次のようになります。-eが設定されていても、関数内のコマンド置換は失敗時にスクリプトを停止しません。、でも少し違うと思います)

「The Line」では、出力を位置引数として渡そうとしますfunc2func1

#!/bin/bash
set -eu -o pipefail -E
shopt -s inherit_errexit

function func1() {
  local arg="${1}"
  echo "(func1)This line shouldn't be reached:arg='${arg}': '${?}'" >&2
}

function func2() {
  echo "value from func2"
  exit 1
}

var="$(func1 "$(func2)")" # The Line
echo "main:This line shouldn't be reached:var='${var}':'${?}'" >&2

プログラマが既存の関数を簡潔に再利用してデータを構造化したい場合が考えられる状況だと思います。

ただし、これにより次の出力が生成されます。

$ bash sandbox.sh 
(func1)This line shouldn't be reached:arg='value from func2': '0'
main:This line shouldn't be reached:var='':'0'
$ 

私はこの動作を呼び出しサブシェルがサブシェルの動作を継承しないfunc2という意味に解釈します。したがって、設定されていない値をパラメータとして使用します。errexitfunc1func1

「The Line」を次のように修正して動作させようとしました。

# A.
var="$(set -e; func1 "$(set -e; func2)")" # The Line

または

# B.
var="$(func1 "$(func2 || exit 1)" || exit 1)" # The Line

しかし、まだ運がありません。実際、A.とB.の両方がこの行動を修正しなかったという事実は混乱しています。これに関するベストプラクティスは何ですか?それとも、最初からコマンド置換を入れ子にしないでください。

答え1

これまでの回答(削除されたように見える)と私が行った実験に基づいて、これ(入れ子になった命令の置き換え)は、せいぜい「悪い習慣」または「非常に面倒」と見なすことができると考え始めました。 。

私ができる最善は

#!/bin/bash
set -eu -o pipefail -E
shopt -s inherit_errexit

function func1() {
  [[ $? == 0 ]]; return 1 # The boilerplate line
  local arg="${1}"
  echo "(func1)This line shouldn't be reached:arg='${arg}': '${?}'" >&2
}

function func2() {
  echo "value from func2"
  return 1
}

var="$(func1 "$(func2)")" # The line
echo "main:This line shouldn't be reached:var='${var}':'${?}'" >&2

これにより、予想される出力(空)とゼロ以外の終了コード(1)が生成されます。秘密は、$?それが0関数定義の最初の行にあることを確認することです。これは、入れ子になったコマンド置換で再利用される各関数定義に挿入する必要があります。不安を感じずに「入れ子になったコマンドの置き換え」構文を使用するには、新しい関数を作成するたびに定型句を挿入する方法に精通している必要があります。

「ボイラーライン」は本当に無害ですか?もちろんそうです。はい。set -euどちらも障害が検出されるたびにすぐに中断されるためです。しかし、私はbashプログラミングに精通していないので、潜在的な落とし穴を逃したかもしれません。もっと経験豊富な人の洞察を知りたいです。よろしくお願いします。

関連情報