🐚

export VAR=$(...)でコマンド置換内のコマンドが失敗してもset -eが効かない理由と対処方法

に公開

概要

変数の値をコマンド置換で設定しながらexportすると、コマンド置換で実行したコマンドが失敗した場合でもset -eが効かず、スクリプトが異常終了しない。

例えば以下のスクリプトは、hoge.txtが存在しない場合でも異常終了しない。

#!/usr/bin/env bash
set -e
export HOGE=$(cat hoge.txt)

これは、cat hoge.txtの終了ステータスが無視され、exportコマンドの終了ステータスが適用されるためである。
hoge.txtが存在しない場合にスクリプトを異常終了させたい場合は、以下のようにする必要がある。

#!/usr/bin/env bash
set -e
HOGE=$(cat hoge.txt)
export HOGE

この書き方の場合、cat hoge.txtの終了ステータスが適用されるためスクリプトを異常終了させることができる。

背景

  • シェルスクリプトの実行中に何らかの異常が発生した場合、スクリプトを途中で異常終了させたいときがある。
  • このときset -eをすると、スクリプト内のコマンドが失敗した場合に異常終了させることができる。

事象

以下のようなスクリプトだと、hoge.txtが存在しない場合でもスクリプトは異常終了しない。

hoge.sh
#!/usr/bin/env bash
set -e
export HOGE=$(cat hoge.txt)
$ ls
hoge.sh   # hoge.txtは存在しない

$ ./hoge.sh
cat: hoge.txt: No such file or directory    # エラーメッセージは表示される

$ echo $?
0   # 終了コードは0 = 異常終了していない

なぜ異常終了しないのか?

export HOGE=$(cat hoge.txt)の終了ステータスには、exportコマンドの終了ステータスが適用される。そのため、cat hoge.txtの終了ステータスは無視される。

Bashマニュアルの3.7.1 Simple Command Expansionに、コマンド置換の終了ステータスが適用される条件が記載されている。

If there is a command name left after expansion, execution proceeds as described below. Otherwise, the command exits. If one of the expansions contained a command substitution, the exit status of the command is the exit status of the last command substitution performed. If there were no command substitutions, the command exits with a zero status.

export HOGE=$(cat hoge.txt)は展開後にexportというコマンド名が残るので、コマンド置換の終了ステータスは適用されない。
HOGE=$(cat hoge.txt)のような変数代入などの展開後にコマンドが残らない場合にのみ、コマンド置換の終了ステータスが適用される。

注釈:「described below」は何を指している?

先ほど引用した文章を読んでいて少し混乱したが、

If there is a command name left after expansion, execution proceeds as described below.

「展開後にコマンド名が残る場合、実行は以下のように進む。」の「以下」というのは、引用した文章に書いてある説明のことではなく、おそらく次節3.7.2 Command Search and Executionに記載してある、コマンドがどのように実行されていくのかを説明してある文章が該当すると思われる。

したがって、

If one of the expansions contained a command substitution, ...

の説明は「Otherwise, the command exits.」にかかっており、コマンドが残らない場合の説明が記載されているものとして解釈した。

最初この文章を読んだときは、「described below」が「If one of the expansions contained a command substitution, ...」を指していると思っていたので、「コマンド名が残る場合、コマンド置換の終了ステータスが適用される」と逆の意味に解釈していた...。

参考:
https://stackoverflow.com/a/48486983

対処方法

今回のシェルスクリプトでhoge.txtが存在しない場合にスクリプトを異常終了させるにはどうすればよいか?

先ほど記載したが、HOGE=$(cat hoge.txt)は展開後にコマンドが残らないため、コマンド置換の終了ステータスが適用される。cat hoge.txthoge.txtが存在しない場合に終了ステータス1で失敗するので、set -eの効果でスクリプトを異常終了させることができる。

つまり、以下のように変数代入とexportを分ければよい。

#!/usr/bin/env bash
set -e
HOGE=$(cat hoge.txt)
export HOGE

Discussion