Bashスクリプトで、特定の条件が発生した場合にスクリプト全体を終了するにはどうすればよいですか?


717

いくつかのコードをテストするスクリプトをBashで作成しています。ただし、コードのコンパイルが最初に失敗した場合にテストを実行するのはばかげているようです。その場合、テストを中止します。

スクリプト全体をwhileループ内にラップしてブレークを使用せずにこれを行う方法はありますか?dun dun dun goto みたいな?

回答:


810

このステートメントを試してください:

exit 1

1適切なエラーコードに置き換えます。特別な意味を持つ終了コードも参照してください。


4
@CMCDragonkai、通常はゼロ以外のコードでも機能します。特別なものが必要ない場合は、1一貫して使用できます。スクリプトが別のスクリプトによって実行されることを意図している場合、特定の意味を持つ独自のステータスコードのセットを定義することができます。たとえば、1==テストが失敗した、2==コンパイルが失敗した。スクリプトが他のものの一部である場合、そこで使用されているプラ​​クティスに一致するようにコードを調整する必要がある場合があります。たとえば、automakeによって実行されたテストスイートの一部である場合、コード77は、スキップされたテストをマークするために使用されます。
のMichałGórny

21
いいえ、スクリプトを終了するだけでなく、ウィンドウも閉じます
Toni Leigh

7
@ToniLeigh bashには「ウィンドウ」の概念がありません。特定のセットアップ(端末エミュレータなど)が何を行うかについて、おそらく混乱しています。
Michael Foukarakis

4
@ToniLeigh「ウィンドウ」を閉じる場合exit #は、スクリプトではなく関数内にコマンドを配置している可能性があります。(その場合はreturn #代わりに使用してください。)
Jamie

1
@Sevenearths 0は成功したことを意味するためexit 0、スクリプトを終了して0を返します(成功したこのスクリプトの結果を使用する可能性のある他のスクリプトに伝える)
VictorGalisson

689

set -eを使用します

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

スクリプトは、失敗した最初の行の後で終了します(ゼロ以外の終了コードを返します)。この場合、command-that-fails2は実行されません。

すべてのコマンドの戻りステータスを確認すると、スクリプトは次のようになります。

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

セット-e、それは次のようになります。

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

コマンドが失敗すると、スクリプト全体が失敗し、$?で確認できる終了ステータスが返されます。スクリプトが非常に長い場合、または多くのものを作成している場合、どこにでもリターンステータスチェックを追加すると、スクリプトがかなり醜くなります。


10
set -eあなたはまだ可能にするいくつかのスクリプトを停止せずにエラーが発生したコマンドの終了を:command 2>&1 || echo $?
Adobe、

6
set -eパイプラインまたはコマンド構造がゼロ以外の値を返す場合、スクリプトは中止されます。たとえばfoo || bar、両方がゼロ以外の値foobar返す場合のみ失敗します。通常、適切に記述されたbashスクリプトはset -e、最初に追加した場合に機能し、追加は自動健全性チェックとして機能します。問題が発生した場合はスクリプトを中止します。
ミッコランタライネン2013

6
コマンドを一緒にパイプ処理している場合、set -o pipefailオプションを設定することにより、コマンドのいずれかが失敗した場合にも失敗する可能性があります。
Jake Biesinger 2013

18
実際にはなしの慣用的なコードset -eはちょうどでしょうmake || exit $?
tripleee 2013

4
また、あなたが持っていset -uます。見てみましょう非公式bashのstrictモードset -euo pipefail
Pablo A

236

SysOpsの人がかつて私に3本指の爪のテクニックを教えました:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

これらの関数は* NIX OSであり、シェルのフレーバーに対応しています。それらをスクリプト(bashなど)の最初に置きtry()、ステートメントとコードをオンにします。

説明

飛行羊のコメントに基づく )。

  • yell:スクリプト名とすべての引数をstderr
    • $0 スクリプトへのパスです。
    • $* すべて引数です。
    • >&2>stdoutを&pipeにリダイレクト2することを意味します。パイプ1stdout自体になります。
  • dieと同じですがyell、「失敗」を意味する0以外の終了ステータス終了します
  • try||(boolean OR)を使用します。これは、左側が失敗した場合にのみ右側を評価します。
    • $@再びすべての引数ですが、異なります。

3
私はUNIXスクリプトにかなり慣れていません。上記の機能がどのように実行されるか説明していただけますか?慣れていない新しい構文がいくつかあります。ありがとう。
kaizenCoder 2015年

15
yell$0スクリプトへのパスです。$*すべて引数です。>&2>標準出力を&パイプにリダイレクトする」という意味2です。パイプ1はstdout自体です。したがって、yellはすべての引数の前にスクリプト名を付け、stderrに出力します。dieyellと同じですが、「失敗」を意味する0以外の終了ステータスで終了します。tryはブール値orを使用||します。これは、左側が失敗しなかった場合にのみ右側を評価します。$@再びすべての引数ですが、異なります。それがすべてを説明することを願っています
飛ぶ羊

1
これを変更してdie() { yell "$1"; exit $2; }、でメッセージを渡し、コードを終了できるようにしましたdie "divide by zero" 115
Mark Lakata、2016年

3
私が使用したいどのように見ることができるyelldie。しかし、tryそれほどではありません。使用例を教えてください。
kshenoy

2
ええと、どのようにスクリプトで使用しますか?"try()your statement and code on"の意味がわかりません。
TheJavaGuy-IvanMilosavljević

33

でスクリプトを呼び出す場合は、スクリプトの終了ステータスをどこでsource使用するreturn <x><x>を指定できます(エラーまたはfalseにはゼロ以外の値を使用します)。しかし、実行可能スクリプトを呼び出すと(つまり、ファイル名で直接)、returnステートメントは不平を表示します(エラーメッセージ「return:関数またはソーススクリプトからのみ「戻る」ことができます)。

場合はexit <x>代わりに使用されているスクリプトがで呼び出されたとき、sourceそれはスクリプトを開始したシェルを終了になりますが、期待通りに実行可能スクリプトは、ちょうど、終了します。

同じスクリプトでどちらの場合も処理するには、次を使用できます

return <x> 2> /dev/null || exit <x>

これは、適切な呼び出しを処理します。これは、スクリプトのトップレベルでこのステートメントを使用することを前提としています。関数内からスクリプトを直接終了しないことをお勧めします。

注意: <x>は単なる数値と見なされます。


関数内でreturn / exitを使用するスクリプトでは機能しません。つまり、シェルを存在させずに関数の内部で終了することはできますが、関数の呼び出し元に関数の戻りコードの正しいチェックを行わせる必要はありません。 ?
2018

@jan戻り値を使用して呼び出し元が行う(または行わない)ことは、関数からどのように戻るか(つまり、呼び出しに関係なくシェルを終了しない場合)と完全に直交します(つまり、独立しています)。これはに、このQ&Aの一部ではない呼び出し元のコード依存します。関数の戻り値を呼び出し元のニーズに合わせて調整することもできますが、この答えこの戻り値が何であるかを制限するものではありません ...
kavadias

11

エラーを処理するために、run()という関数を含めることがよくあります。実行するすべての呼び出しはこの関数に渡されるので、エラーが発生するとスクリプト全体が終了します。これがset -eソリューションよりも優れている点は、行が失敗してもスクリプトが警告なしに終了せず、問題が何であるかを通知できることです。次の例では、スクリプトがfalseの呼び出しで終了するため、3行目は実行されません。

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

1
男、どういうわけか、私はこの答えが本当に好きです。少し複雑ですが、とても便利なようです。そして、私はbashの専門家ではないので、私の論理には欠陥があり、この方法論に何か問題があると信じるようになります。それで、この関数の何が問題なのでしょうか?ここで注意すべきことはありますか?

evalを使用した理由を思い出せません。この関数はcmd_output = $($ 1)で正常に機能します
Joseph Sheedy

私はこれを複雑なデプロイプロセスの一部として実装しただけで、すばらしい動作をしました。ありがとう、コメントと賛成票はこちらです。
fuzzygroup 2017年

本当に素晴らしい作品です!これは、うまく機能する最も単純でクリーンなソリューションです。私にとっては、FORループがを取得しないため、FORループのコマンドの前にこれを追加しましたset -e option。次に、コマンドには引数が付いているため、次のようなbashの問題を回避するために一重引用符を使用しましたrunTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'。注関数の名前をrunTryに変更しました。
Tony-Caffe

1
eval任意の入力を受け入れると潜在的に危険ですが、それ以外の場合はかなり見栄えがします。
dragon788

5

構成の代わりにif短絡評価を活用できます。

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

代替演算子の優先度のために必要な括弧のペアに注意してください。$?最近呼び出されたコマンドの終了コードに設定される特別な変数です。


1
command -that --fails || exit $?かっこなしで機能する場合、それecho $[4/0]が必要になるのはなぜですか?
エントロピー2015年

2
@Anentropic @skalee括弧は優先順位とは関係ありませんが、例外処理です。ゼロ除算は(すなわち、シンプル括弧なしでコード1とシェルからの即時終了を引き起こしますecho $[4/0] || exit $?bashが実行されることはありませんecho、ましてやオベイ||
bobbogo

1

同じ質問がありますが、重複するため質問できません。

スクリプトがもう少し複雑な場合、exitを使用して受け入れられた回答は機能しません。バックグラウンドプロセスを使用して状態を確認する場合、exitはサブシェルで実行されるため、そのプロセスのみを終了します。スクリプトを強制終了するには、明示的に強制終了する必要があります(少なくとも、それが私が知っている唯一の方法です)。

これを行う方法についての簡単なスクリプトは次のとおりです。

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

これはより良い答えですが、まだ不完全ですa私はブームの部分を取り除く方法を本当に知りません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.