web-dev-qa-db-ja.com

Bashスクリプトでset -eとはどういう意味ですか?

このパッケージがそのDebianアーカイブ(.deb)ファイルから解凍される前にスクリプトが実行するこの preinst ファイルの内容を調べています。

スクリプトのコードは以下のとおりです。

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

私の最初の質問はラインについてです:

set -e

スクリプトの残りの部分は非常に単純だと思います。Debian/ Ubuntuパッケージマネージャがインストール操作を実行しているかどうかをチェックします。もしそうなら、それは私のアプリケーションがちょうどシステムにインストールされたかどうかをチェックします。もしあれば、スクリプトは "MyApplicationNameはインストールされました" というメッセージを表示して終了します(return 1は「エラー」で終わることを意味しますか?).

ユーザがDebian/Ubuntuパッケージシステムに自分のパッケージをインストールするように要求している場合、スクリプトは2つのディレクトリも削除します。

これは正しいですか、私は何かが足りないのですか?

528
AndreaNobili

help setから:

  -e  Exit immediately if a command exits with a non-zero status.

しかし、それはいくつかの人によって悪い習慣と考えられています(bash FAQとirc freenode #bash FAQ作者)。それを使用することをお勧めします:

trap 'do_something' ERR

エラーが発生したときにdo_something関数を実行します。

http://mywiki.wooledge.org/BashFAQ/105を参照してください

605
Gilles Quenot

コマンドまたはパイプラインにエラーがある場合、set -eはスクリプトの実行を停止します。これは、シェルのデフォルトの動作(スクリプト内のエラーを無視すること)の反対です。この組み込みコマンドの資料を参照するには、端末にhelp setと入力してください。

74
Robin Green

bash - The Set Builtin manualによると、-e/errexitが設定されている場合、 パイプライン が単一の 単純コマンドリスト または 複合コマンド は0以外のステータスを返します。

デフォルトでは、パイプラインの終了ステータスは、pipefailオプションが有効になっていない限り(デフォルトでは無効になっています)、パイプラインの最後のコマンドの終了ステータスです。

そうである場合、パイプラインは最後(一番右)のコマンドのステータスをゼロ以外のステータスで返します。すべてのコマンドが正常に終了した場合はゼロを返します。

終了時に何かを実行したい場合は、以下のようにtrapを定義してみてください。

trap onexit EXIT

ここでonexitは終了時に何かをするための関数です。以下のように単純な スタックトレース を出力します。

onexit(){ while caller $((n++)); do :; done; }

同様のオプション -E/errtrace があり、代わりにERRをトラップします。例えば:

trap onerr ERR

ゼロステータスの例:

$ true; echo $?
0

ゼロ以外の状況の例

$ false; echo $?
1

否定的なステータス例:

$ ! false; echo $?
0
$ false || true; echo $?
0

pipefailを無効にしてテストします。

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

pipefailを有効にしてテストします。

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
43
kenorb

Googlingでこの質問を見つけました。set -eが原因で中止されたスクリプトの終了ステータスが何であるかを見つけようとしました。答えは私には明白に見えませんでした。それ故にこの答え。基本的に、set -eはコマンド(シェルスクリプトなど)の実行を中止し、失敗したコマンドの終了ステータスコード(つまり、外部スクリプトではなく内部スクリプト)を返します

たとえば、シェルスクリプトouter-test.shがあるとします。

#!/bin/sh
set -e
./inner-test.sh
exit 62;

inner-test.shのコードは次のとおりです。

#!/bin/sh
exit 26;

コマンドラインからouter-script.shを実行すると、外側のスクリプトが内側のスクリプトの終了コードで終了します。

$ ./outer-test.sh
$ echo $?
26
41
entpnerd

その意図は、問題のスクリプトが早く失敗することだと私は信じています。

これを自分でテストするには、単純にbashプロンプトでset -eを入力してください。それでは、lsを実行してみてください。ディレクトリ一覧が表示されます。それでは、lsdと入力してください。そのコマンドは認識されずエラーコードを返すので、あなたのbashプロンプトは閉じます(set -eのため)。

さて、これを「スクリプト」の文脈で理解するために、この単純なスクリプトを使用してください。

#!/bin/bash 
# set -e

lsd 

ls

そのまま実行すると、最後の行のlsからディレクトリの一覧が表示されます。 set -eのコメントを外して再度実行した場合、bashがlsdからのエラーに遭遇すると処理が停止するため、ディレクトリのリストは表示されません。

5

これは昔からの質問ですが、ここでの答えはどれもDebianパッケージ処理スクリプトでのset -eまたはset -o errexitの使用について議論しているものではありません。これらのスクリプトでは、このオプションの使用は 必須 Debianポリシーに準拠しています。その意図は明らかに未処理のエラー状態の可能性を回避することです。

これが実際に意味することは、あなたが実行するコマンドがどのような条件の下でエラーを返すかもしれないかを理解しなければならず、そしてそれらの各エラーを明示的に扱うということです。

一般的な問題はdiff(違いがあるとエラーを返します)とgrep(一致がないとエラーを返します)。明示的な処理でエラーを回避できます。

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(また、現在のスクリプトの名前をメッセージに含め、診断メッセージを標準出力ではなく標準エラーに書き出すように注意してください。)

明示的な処理が本当に必要でも有用でもない場合は、明示的に何もしないでください。

diff this that || true
grep cat food || :

(シェルの: no-opコマンドの使用は少しあいまいですが、かなり一般的に見られます。)

繰り返しますが

something || other

の省略形です

if something; then
    : nothing
else
    other
fi

つまり、otherが失敗した場合に限り、somethingが実行されるべきであると明示的に言います。省略形のif(およびwhileuntilなどの他のシェルフロー制御ステートメント)もエラーを処理するための有効な方法です(実際にそうでない場合は、set -eを持つシェルスクリプトにフロー制御ステートメントを含めることはできません)。

また、明示的に言うと、このようなハンドラーがない場合、diffに違いが見つかった場合、またはgrepに一致が見つからなかった場合、set -eによってスクリプト全体が直ちにエラーで失敗します。

一方、いくつかのコマンドでは、必要なときにエラー終了ステータスを生成しません。一般的に問題のあるコマンドはfind(終了ステータスはファイルが実際に見つかったかどうかを反映しない)とsed(終了ステータスはスクリプトが入力を受け取ったか実際にコマンドを正常に実行したかを明らかにしません)です。いくつかのシナリオでの簡単な保護は、出力がない場合に悲鳴を上げるコマンドにパイプすることです。

find things | grep .
sed -e 's/o/me/' stuff | grep ^

パイプラインの終了ステータスは、そのパイプラインの最後のコマンドの終了ステータスです。そのため、上記のコマンドは実際にはfindsedのステータスを完全に隠し、grepが最後に成功したかどうかだけを示します。

(もちろん、Bashはset -o pipefailを持っています;しかしDebianパッケージスクリプトはBash機能を使うことができません。これらのスクリプトにPOSIXのshを使うことをポリシーがしっかりと定めていますが、いつもそうとは限りません。)

多くの場合、これは防御的にコーディングするときに注意することです。時にはあなたがする必要があります。一時ファイルを調べて、その出力を生成したコマンドが、慣用句や便利さからシェルパイプラインを使用するように指示された場合でも、正常に終了したかどうかを確認できます。

2
tripleee
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" Shell will process and program exit, it will not proceed further
0
Manikandan Raj