Автоматический выход из скрипта оболочки bash при ошибке - PullRequest
534 голосов
/ 20 мая 2010

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

#!/bin/bash  

cd some_dir  

./configure --some-flags  

make  

make install

Так что в этом случае, если сценарий не может перейти в указанный каталог, он, конечно, не захочет делать ./configure впоследствии, если он потерпит неудачу.

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

Ответы [ 8 ]

833 голосов
/ 20 мая 2010

Используйте встроенный 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 - при сбоях командной строки. Некоторые ошибки и обходные пути хорошо документированы здесь .

(*) Примечание:

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

(из man bash)

62 голосов
/ 20 мая 2010

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

set -e

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

47 голосов
/ 06 марта 2014

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

#!/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 *** 
************
'
31 голосов
/ 18 мая 2016

Используйте его вместе с pipefail.

set -e
set -o pipefail

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

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

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

21 голосов
/ 14 августа 2015

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

#!/bin/bash -e

cd some_dir  

./configure --some-flags  

make  

make install
18 голосов
/ 20 мая 2010

Одна идиома:

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

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

17 голосов
/ 20 мая 2010

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

trap command signal [signal ...]

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

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

0 голосов
/ 12 марта 2019

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

-E

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


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

В соответствии с инструкцией set -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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...