Как выйти, если команда не прошла?

222

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

my_command && (echo 'my_command failed; exit)

Но это не работает. Он продолжает выполнять инструкции, следующие за этой строкой в ​​скрипте. Я использую Ubuntu и Bash.

user459246
источник
5
Вы предполагали, что открытая кавычка будет синтаксической ошибкой, которая вызовет сбой / выход? если нет, вы должны закрыть цитату в вашем примере.
варенье

Ответы:

406

Пытаться:

my_command || { echo 'my_command failed' ; exit 1; }

Четыре изменения:

  • Изменить &&на||
  • Используйте { }вместо( )
  • Ввести ;после exitи
  • пробелы после {и до}

Так как вы хотите напечатать сообщение и выйти только в случае сбоя команды (выход с ненулевым значением), вам не нужно ||указывать &&.

cmd1 && cmd2

будет работать cmd2при cmd1успешном завершении (выходное значение 0). В то время как

cmd1 || cmd2

будет работать cmd2при cmd1сбое (выходное значение ненулевое).

Использование ( )заставляет команду внутри них запускаться в под-оболочке, а вызов exitоттуда приводит к выходу из под-оболочки, а не к исходной оболочке, следовательно, выполнение продолжается в исходной оболочке.

Чтобы преодолеть это использование { }

Последние два изменения требуются bash.

codaddict
источник
4
Похоже, что это "обратное". Если функция "завершается успешно", она возвращает 0, а если она "завершается неудачей", она возвращает ненулевое значение, поэтому можно ожидать, что && оценит, когда первая половина вернула ненулевое значение. Это не означает, что ответ выше неверен - нет, это правильно. && и || в скриптах работа основана на успехе, а не на возвращаемом значении.
CashCow
7
Это кажется перевернутым, но прочитайте это, и это имеет смысл: «выполнить эту команду (успешно)» ИЛИ «распечатать эту ошибку и выйти»
simpleuser
2
Логика заключается в том, что язык использует оценку короткого замыкания (SCE). С SCE, f выражение имеет форму "p ИЛИ q", и p оценивается как истинное, тогда нет никаких оснований даже смотреть на q. Если выражение имеет форму «p AND q», а p оценивается как ложное, нет причины смотреть на q. Причина этого двоякая: 1) она быстрее по понятным причинам и 2) она избегает ошибок определенного типа (например: «если x! = 0 И 10 / x> 5» произойдет сбой при отсутствии SCE ). Возможность использовать его в командной строке, как это, является счастливым побочным эффектом.
user2635263
2
Я бы порекомендовал { (>&2 echo 'my_command failed') ; exit 1; }
повторить
127

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

Daenyth
источник
3
К сожалению, это также очень опасно - set -eимеет длинный и загадочный набор правил о том, когда он работает, а когда нет. (Если кто-то запускается if yourfunction; then ..., то set -eникогда не будет запускаться внутри yourfunctionили чем-то, что он вызывает, потому что этот код считается «проверенным»). Смотрите раздел упражнений BashFAQ # 105 , обсуждая эту и другие ловушки (некоторые из которых относятся только к определенным версиям оболочки, поэтому вы должны быть уверены, что проверили каждый выпуск, который может быть использован для запуска вашего скрипта).
Чарльз Даффи
67

Также обратите внимание, что состояние выхода каждой команды хранится в переменной оболочки $?, Которую вы можете проверить сразу после запуска команды. Ненулевое состояние означает сбой:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Алекс Хованский
источник
10
Это можно просто заменить, если my_command, здесь нет необходимости использовать тестовую команду.
Барт Сас
5
+1, потому что я думаю, что вы не должны быть наказаны за перечисление этой альтернативы - она ​​должна быть на столе - хотя это немного уродливо и по моему опыту слишком легко выполнить другую команду между ними, не замечая природу теста (может быть, я м просто тупой).
Тони Делрой
1
@BartSas Если команда длинная, лучше поместить ее в отдельную строку. Это делает скрипт более читабельным.
Майкл
@ Майкл, нет, если он создает зависимости между строками, где вам нужно знать, что произошло в строке A, прежде чем вы сможете понять строку B (или, что еще хуже, нужно знать, что произойдет в строке B, прежде чем вы знаете, что безопасно что-то добавить новый после конца строки А). Я видел слишком много раз, когда кто-то добавил echo "Just finished step A", не зная, что это echoпереписало то, $?что будет проверено позже.
Чарльз Даффи
67

Если вы хотите такое поведение для всех команд в вашем скрипте, просто добавьте

  set -e 
  set -o pipefail

в начале сценария. Эта пара опций указывает интерпретатору bash выходить всякий раз, когда команда возвращается с ненулевым кодом выхода.

Это не позволяет вам распечатать сообщение о выходе.

damienfrancois
источник
8
Вы можете запускать команды при выходе, используя trapвстроенную команду bash.
Гэвин Смит
Это ответ на вопрос XY.
JWG
5
Может быть интересно объяснить, что команды делают независимо, а не пару. Тем более, что set -o pipefailможет не быть желаемого поведения. Еще спасибо за указание на это!
Антуан Пинсард
3
set -eне совсем надежный, последовательный или предсказуемый! Чтобы убедиться в этом, просмотрите раздел упражнений BashFAQ # 105 или таблицу сравнения поведения различных оболочек на in-ulm.de/~mascheck/various/set-e
Чарльз Даффи,
14

Я взломал следующую идиому:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Перед каждой командой следует информационное эхо, а за каждой командой следует та же
if [ $? -ne 0 ];...строка. (Конечно, вы можете отредактировать это сообщение об ошибке, если хотите.)

Грант Бирчмейер
источник
Это может быть определено как функция, поэтому используйте ее повторно.
Эрик Ван
1
'если [$? -не 0]; тогда ... фи 'это сложный способ написать' || '
Кевин Клайн
if ! javac *.java; then ...- зачем $?вообще?
Чарльз Даффи
Чарльз - потому что я в лучшем случае посредственный сценарист bash :)
Грант Бирчмайер,
10

При условии, что my_commandон канонически спроектирован, то есть возвращает 0 в случае успеха, тогда &&это полная противоположность того, что вы хотите. Вы хотите ||.

Также обратите внимание, что (в bash мне это не кажется правильным, но я не могу попробовать оттуда, где я нахожусь. Скажи-ка.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
источник
IIRC, вам нужны точки с запятой после каждой строки внутри {}
Алекс Ховански,
9
@ Алекс: нет, если они на отдельной строке.
Приостановлено до дальнейшего уведомления.
5

Вы также можете использовать, если вы хотите сохранить статус ошибки выхода, и иметь читаемый файл с одной командой на строку:

my_command1 || exit $?
my_command2 || exit $?

Это, однако, не будет печатать никаких дополнительных сообщений об ошибках. Но в некоторых случаях ошибка все равно будет напечатана неудачной командой.

alexpirine
источник
1
Там нет причин для этого быть exit $?; просто exitиспользует в $?качестве значения по умолчанию.
Чарльз Даффи
@CharlesDuffy не знал. Круто, так еще проще!
alexpirine
3

trapВстроенная оболочка позволяет ловить сигналы, а также другие полезные условия, в том числе не удалось выполнения команды (т.е. статус возврата не равен нулю). Поэтому, если вы не хотите явно проверять состояние возврата каждой отдельной команды, вы можете сказать, trap "your shell code" ERRи код оболочки будет выполняться каждый раз, когда команда возвращает ненулевой статус. Например:

trap "echo script failed; exit 1" ERR

Обратите внимание, что, как и в других случаях перехвата неудачных команд, конвейеры нуждаются в особой обработке; выше не поймаешь false | true.

Козел
источник