Автоматический выход из bash оболочки script при ошибке

Я писал некоторую оболочку script, и я бы счел ее полезной, если бы была возможность остановить выполнение указанной оболочки script, если какая-либо из команд не удалась. Ниже приведен пример:

#!/bin/bash  

cd some_dir  

./configure --some-flags  

make  

make install

Таким образом, в этом случае, если script не может измениться в указанную директорию, тогда он, конечно, не захочет делать. /configure после этого.

Теперь я хорошо знаю, что у меня могла бы быть проверка if для каждой команды (которая, по моему мнению, является безнадежным решением), но существует ли глобальная настройка для выхода script, если одна из команд выходит из строя?

Ответ 1

Используйте встроенный set -e:

#!/bin/bash
set -e
# Any subsequent(*) commands which fail will cause the shell script to exit immediately

Также вы можете передать -e в командной строке:

bash -e my_script.sh

Вы также можете отключить это поведение с помощью set +e.

Вы также можете использовать все или некоторые -e -u -x и -o pipefail, например:

set -euxo pipefail

-e завершается при ошибке, -u по неопределенным переменным, а -o (for option) pipefail завершается при сбоях командной трубы. Некоторые ошибки и обходные пути хорошо описаны здесь.

(*) Заметка:

Оболочка не завершает работу, если сбойная команда является частью списка команд, следующих сразу за ключевым словом некоторое время или до, часть теста после зарезервированных слов if или elif, часть любой команды, выполненной в символах && или || список, кроме команды, следующей за последним символом && или || любая команда в конвейере, кроме последней, или если возвращаемое значение команды инвертируется с помощью !

(от man bash)

Ответ 2

Чтобы выйти из script, как только одна из команд не удалась, добавьте это в начале:

set -e

Это приводит к тому, что script выходит немедленно, когда некоторая команда, которая не является частью какого-либо теста (например, в if [ ... ] или конструкторе &&), выходит с ненулевым кодом выхода.

Ответ 3

Вот как это сделать:

#!/bin/sh

abort()
{
    echo >&2 '
***************
*** ABORTED ***
***************
'
    echo "An error occurred. Exiting..." >&2
    exit 1
}

trap 'abort' 0

set -e

# Add your script below....
# If an error occurs, the abort() function will be called.
#----------------------------------------------------------
# ===> Your script goes here
# Done!
trap : 0

echo >&2 '
************
*** DONE *** 
************
'

Ответ 4

Используйте его в сочетании с pipefail.

set -e
set -o pipefail

-e (errexit): прервать выполнение сценария при первой ошибке, когда команда завершает работу с ненулевым состоянием (за исключением циклов before или while, if-tests, конструкций списка)

-o pipefail: заставляет конвейер возвращать состояние выхода последней команды в канале, которая возвратила ненулевое возвращаемое значение.

Глава 33. Опции

Ответ 5

Альтернатива принятому ответу, который подходит в первой строке:

#!/bin/bash -e

cd some_dir  

./configure --some-flags  

make  

make install

Ответ 6

Одна идиома:

cd some_dir && ./configure --some-flags && make && make install

Я понимаю, что это может затянуться, но для больших скриптов вы можете разбить его на логические функции.

Ответ 7

Я думаю, что вы ищете команду trap:

trap command signal [signal ...]

Для получения дополнительной информации см. эту страницу.

Другим вариантом является использование команды set -e в верхней части вашего script - это приведет к завершению script, если какая-либо программа/команда вернет неверное значение.

Ответ 8

В существующих ответах упущен один момент - показать, как наследовать ошибки. Оболочка bash предоставляет один такой вариант для этого с помощью set

-E

Если установлено, любая ловушка в ERR наследуется функциями оболочки, подстановками команд и командами, выполняемыми в среде подоболочки. Ловушка ERR обычно не наследуется в таких случаях.


Ответ Адама Розенфилда на рекомендацию использовать set -E прав в некоторых случаях, но у него есть свои потенциальные подводные камни. См. GreyCat BashFAQ - 105 - Почему не установить -E (или установить -o errexit или trap ERR), что я ожидал?

Согласно инструкции, набор -E выходит

если простой коммандекс с ненулевым статусом. Оболочка не завершает работу, если сбойная команда является частью списка команд, следующих сразу за ключевым словом некоторое while или until, часть test in a if statement, часть && или || список, кроме команды, следующей за final && or || any command in a pipeline but the last, или если возвращаемое значение команды инвертируется через ! ".

это означает, что set -E не работает в следующих простых случаях (подробные объяснения можно найти в вики)

  1. Использование арифметического оператора let или $((..)) (начиная с bash 4.1) для увеличения значения переменной как

    #!/usr/bin/env bash
    set -e
    i=0
    let i++                   # or ((i++)) on bash 4.1 or later
    echo "i is $i" 
    
  2. Если нарушающая команда не является частью последней команды, выполненной через && или || , Например, приведенная ниже ловушка не сработает, когда ожидается

    #!/usr/bin/env bash
    set -e
    test -d nosuchdir && echo no dir
    echo survived
    
  3. При неправильном использовании в операторе if as код завершения оператора if является кодом выхода последней выполненной команды. В приведенном ниже примере последней выполненной командой был echo который не сработал бы, даже если test -d не прошел

    #!/usr/bin/env bash
    set -e
    f() { if test -d nosuchdir; then echo no dir; fi; }
    f 
    echo survived
    
  4. При использовании с подстановкой команд они игнорируются, если только для inherit_errexit не установлено bash 4.4.

    #!/usr/bin/env bash
    set -e
    foo=$(expr 1-1; true)
    echo survived
    
  5. когда вы используете команды, которые выглядят как назначения, но не являются такими, как export, declare, typeset или local. Здесь вызов функции f не завершится, так как local обнаружил код ошибки, который был установлен ранее.

    set -e
    f() { local var=$(somecommand that fails); }        
    g() { local var; var=$(somecommand that fails); }
    
  6. Когда используется в конвейере, и команда-нарушитель не является частью последней команды. Например, приведенная ниже команда все равно будет проходить. Один из вариантов - включить pipefail, возвращая код завершения первого неудачного процесса:

    set -e
    somecommand that fails | cat -
    echo survived
    

Идеальная рекомендация - не использовать set -E и использовать вместо этого собственную версию проверки ошибок. Более подробная информация о реализации пользовательской обработки ошибок в одном из моих ответов на сообщение об ошибке в скрипте Bash