web-dev-qa-db-ja.com

'set -e'は何をするのか、なぜそれが危険だと考えられるのか?

この質問はインタビュー前のクイズに出てきて、私を夢中にさせています。誰かがこれに答えて私を安心させることができますか?クイズには特定のシェルへの参照はありませんが、ジョブの説明はUNIX SA用です。再び質問は単純です...

'set -e'は何をするのか、なぜそれが危険だと考えられるのか?

148
egorgry

set -eサブコマンドまたはパイプラインがゼロ以外のステータスを返した場合、シェルは終了します。

インタビュアーがおそらく探していた答えは次のとおりです。

Init.dスクリプトを作成するときに「set -e」を使用すると危険です。

から http://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2-

Init.dスクリプトでset -eを使用する場合は注意してください。正しいinit.dスクリプトを作成するには、デーモンが既に実行されているか、またはinit.dスクリプトを中止せずにすでに停止している場合に、さまざまなエラー終了ステータスを受け入れる必要があります。 init.dスクリプトの場合、多くの場合、set -eを使用せずに、各コマンドの結果を個別に確認する方が簡単です。

これは、インタビュアーの観点からは有効な質問です。これは、候補者がサーバーレベルのスクリプトと自動化に関する実用的な知識を評価するためです。

156
Rich

bash(1) から:

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see Shell GRAMMAR
                  above) exits with a non-zero status.  The Shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or Elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the Shell exits.  This option
                  applies to the Shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

残念ながら、「スクリプトの残りの部分が実行されない」または「おそらく実際の問題を覆い隠す可能性がある」以外はなぜ危険なのかを考えるほど創造的ではありません。

注意すべきこと set -eは、スクリプトのさまざまなセクションでオンとオフを切り替えることができます。スクリプト全体を実行するためにオンにする必要はありません。条件付きで有効にすることもできます。とはいえ、自分でエラー処理を行う(または行わない)ため、これを使用することはありません。

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

また、注目に値するのは、trapコマンドのBashのmanページのセクションで、set -e特定の状況下で機能します。

失敗したコマンドが、whileまたはuntilキーワードの直後のコマンドリストの一部である場合、ifステートメントのテストの一部、&&または⎪⎪リストで実行されるコマンドの一部、またはコマンドの場合、ERRトラップは実行されません戻り値は!によって反転されています。これらは、errexitオプションが従うのと同じ条件です。

したがって、ゼロ以外のステータスが終了を引き起こさないいくつかの条件があります。

理解できないと危険だと思いますset -eは、何らかの無効な仮定のもとで実行され、そうでない場合に誤ってそれに依存します。

BashFAQ/105も参照してください。なぜ-eを設定しない(または-o errexitを設定しない、またはERRをトラップする)と、期待どおりに動作しませんか?

これは就職の面接のためのクイズであることに注意してください。質問は現在のスタッフによって書かれた可能性があり、間違っている可能性があります。これは必ずしも悪いことではなく、誰もが間違いを犯し、インタビューの質問はしばしばレビューなしで暗いコーナーに置かれ、インタビュー中にのみ出てきます。

「set -e」が「危険」と見なすことを何も実行しない可能性は十分にあります。しかし、その質問の作者は、自分の無知または偏見のために、「set -e」が危険であると誤って信じているかもしれません。多分彼らはバギーなシェルスクリプトを書いて、それはひどく爆破し、実際に適切なエラーチェックを書くことを怠ったときに、誤って「-e」が原因だと思ったのです。

私は過去2年間でおそらく40件の就職の面接に参加しており、面接担当者は時々間違った質問をしたり、間違った答えを出したりします。

あるいは、それはひどい問題かもしれませんが、完全に予想外ではない、トリックの質問です。

あるいは、これは良い説明かもしれません: http://www.mail-archive.com/[email protected]/msg473314.html

13

set -eは、スクリプトでbashに、何かがゼロ以外の戻り値を返したときに終了するように指示します。

何かのアクセス許可を開いていない限り、それがいかに煩わしく、バグが多く、危険かはわかりません。また、アクセス許可を再度制限する前に、スクリプトが停止しました。

9
cpbills

set -eは、特定の状況を除いて、ゼロ以外の終了コードが検出された場合にスクリプトを終了します。その使用の危険性を一言で要約すると、人々の考えているようには動作しません。

私の意見では、これは互換性の目的でのみ存在し続ける危険なハックと見なされるべきです。 set -eステートメントは、シェルをエラーコードを使用する言語から例外のような制御フローを使用する言語に変換しません。単にその動作をエミュレートしようとするだけでは不十分です。

Greg Wooledgeはset -eの危険性について多くのことを述べています:

2番目のリンクには、set -eの直感的で予測できない動作のさまざまな例があります。

set -eの直感的でない動作の例(一部は上記のWikiリンクから取得):

  • set -e 
     x = 0 
     let x ++ 
     echo "x is $ x"
    let x++は0を返し、letキーワードによって偽の値として扱われ、ゼロ以外の終了コードに変換されるため、上記によりシェルスクリプトが途中で終了します。 set -eはこれに気づき、スクリプトをサイレントに終了します。
  •  set -e 
     [-d/opt/foo] && echo "警告:fooはすでにインストールされています。上書きします。" >&2 
     echo "Installing foo ..." 
    

    上記は期待どおりに動作し、/opt/fooがすでに存在する場合は警告を出力します。

     set -e 
     check_previous_install(){
     [-d/opt/foo] && echo "警告:fooはすでにインストールされています。上書きします。" >&2 
    } 
     
     check_previous_install 
     echo "Installing foo ..." 
    

    上記は、単一の行が関数にリファクタリングされているという唯一の違いにもかかわらず、/opt/fooが存在しない場合は終了します。これは、元々機能していたという事実がset -eの動作に対する特別な例外であるためです。 a && bがゼロ以外を返す場合、set -eによって無視されます。ただし、関数であるため、関数の終了コードはそのコマンドの終了コードと同じであり、ゼロ以外の値を返す関数はスクリプトを通知せずに終了します。

  •  set -e 
     IFS = $ '\ n' read -d '' -r -a config_vars <config 
    

    上記は、ファイルconfigから配列config_varsを読み取ります。作者が意図しているように、configがない場合はエラーで終了します。作者が意図していない可能性があるため、configが改行で終わっていない場合は、黙って終了します。ここでset -eを使用しなかった場合、config_varsには、改行で終わっているかどうかに関係なく、ファイルのすべての行が含まれます。

    Sublime Text(および改行を正しく処理しないその他のテキストエディター)のユーザーは注意してください。

  •  set -e 
     
     should_audit_user(){
     local group groups = "$(groups" $ 1 ")" 
     for $ groups ; do 
     if ["$ group" = audit];次に0を返します。 fi 
     done 
     return 1 
    } 
     
     if should_audit_user "$ user";次に
    ロガー 'Blah' 
     fi 
    

    ここで作成者は、何らかの理由でユーザー$userが存在しない場合、groupsコマンドが失敗し、ユーザーが監査されていないタスクを実行するのではなく、スクリプトが終了すると合理的に期待します。ただし、この場合、set -e終了は有効になりません。何らかの理由で$userが見つからない場合、スクリプトを終了する代わりに、should_audit_user関数は、set -eが無効であるかのように、誤ったデータを返します。

    これは、ifステートメントの条件部分から呼び出されるすべての関数に適用されます。ネストの深さがどの程度であっても、どこで定義されていても、その中でset -eを再度実行した場合でも同様です。いつでもifを使用すると、条件ブロックが完全に実行されるまで、set -eの効果が完全に無効になります。作成者がこの落とし穴に気付いていない場合、または関数を呼び出すことができるすべての状況でコールスタック全体がわからない場合は、バグのあるコードを記述し、set -eが提供する誤った安心感が少なくとも部分的に非難すること。

    作成者がこの落とし穴を十分に認識している場合でも、回避策は、set -eなしでコードを書くのと同じ方法でコードを書くことです。著者は、set -eが有効でないかのように手動のエラー処理コードを記述する必要があるだけでなく、set -eの存在により、彼らがだまされている必要はないと考えて騙された可能性があります。

set -eのその他の欠点:

  • それはずさんなコードを奨励します。エラーハンドラーは完全に忘れられており、失敗したエラーが何らかの賢明な方法でエラーを報告することを期待しています。ただし、上記のlet x++などの例では、これは当てはまりません。スクリプトが予期せず終了した場合、通常はサイレントになり、デバッグが妨げられます。スクリプトが停止せず、予期したとおりである場合(前の箇条書きを参照)、手に微妙で潜行性のあるバグがあります。
  • それは人々を誤った安心感へと導きます。 if- conditionの箇条書きをもう一度参照してください。
  • シェルが終了する場所は、シェル間またはシェルバージョン間で一貫していません。 set -eの動作がバージョン間で調整されているため、古いバージョンのbashで異なる動作をするスクリプトを誤って作成する可能性があります。

set -eは論争の的であり、それを取り巻く問題に気づいている人の中にはそれを勧めない人もいます。エラー状態のキャッチオールとしてすべてのスクリプトでset -eを推奨する多くのシェルスクリプト初心者がいますが、実際にはそのようには機能しません。

set -eは、教育に代わるものではありません。

8
Score_Under

スクリプトのフローを制御できないため、危険だと思います。スクリプトが呼び出すコマンドのいずれかがゼロ以外を返す限り、スクリプトは終了できます。したがって、必要なのは、コンポーネントの動作または出力を変更する何かを行うことだけであり、メインスクリプトを終了することができます。それはスタイルの問題のほうが多いかもしれませんが、間違いなく結果があります。もしあなたのメインスクリプトがいくつかのフラグを設定することになっていて、それが早期に終了したためにそうしなかった場合はどうなりますか?フラグがそこにあるはずであると想定したり、予期しないデフォルトまたは古い値を使用したりすると、システムの残りの部分に障害が発生します。

2
Marcin

個人的に私を噛んだ@Marcinの回答のより具体的な例として、スクリプトのどこかにrm foo/bar.txt行があったと想像してください。 foo/bar.txtが実際に存在しなければ、通常は大したことはありません。ただし、set -eが存在する場合、スクリプトはそこで早期に終了します。おっとっと。

0
Suan