В скрипте Bash, как я могу выйти из всего скрипта, если возникает определенное условие?

718

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

Есть ли способ, которым я могу сделать это, не оборачивая весь скрипт внутри цикла while и не используя разрывы? Что-то вроде Дун Дун Дун Гото?

samoz
источник

Ответы:

811

Попробуйте это утверждение:

exit 1

Заменить 1на соответствующие коды ошибок. См. Также Коды выхода с особыми значениями .

Майкл Фукаракис
источник
4
@CMCDragonkai, обычно работает любой ненулевой код. Если вам не нужно ничего особенного, вы можете просто использовать 1последовательно. Если сценарий предназначен для запуска другим сценарием, вы можете определить свой собственный набор кодов состояния с определенным значением. Например, 1== тесты не пройдены, 2== компиляция не удалась. Если сценарий является частью чего-то другого, вам может потребоваться настроить коды в соответствии с используемыми там практиками. Например, когда часть набора тестов запускается automake, код 77используется для обозначения пропущенного теста.
Михал Гурны
21
нет, это тоже закрывает окно, а не просто выход из сценария
Тони Ли
7
@ToniLeigh bash не имеет понятия «окна», вы, вероятно, запутались в отношении того, что делает ваша конкретная установка - например, эмулятор терминала.
Майкл Фукаракис,
4
@ToniLeigh Если он закрывает «окно», скорее всего, вы помещаете exit #команду в функцию, а не в скрипт. (В этом случае используйте return #вместо этого.)
Джейми
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

С set -e это будет выглядеть так:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Любая неудачная команда приведет к сбою всего сценария и вернет состояние выхода, которое вы можете проверить с помощью $? , Если ваш сценарий очень длинный или вы создаете много материала, он будет довольно уродливым, если вы будете добавлять проверки статуса возврата везде.

Shizzmo
источник
10
С set -eвас еще можно сделать некоторые команды выхода с ошибками без остановки сценария: command 2>&1 || echo $?.
Adobe
6
set -eпрервет сценарий, если структура конвейера или команды вернет ненулевое значение. Например foo || bar, потерпит неудачу, только если оба fooи barвернут ненулевое значение. Обычно хорошо написанный сценарий bash будет работать, если вы добавляете set -eв начале, а дополнение работает как автоматическая проверка работоспособности: прервите сценарий, если что-то пойдет не так.
Микко Ранталайнен,
6
Если вы объединяете команды в команду, вы также можете потерпеть неудачу, если какая-либо из них не сработает, установив этот set -o pipefailпараметр.
Джейк Бизингер
18
На самом деле set -eбыл бы просто идиоматический код без make || exit $?.
tripleee
4
Также у вас есть set -u. Посмотрите на неофициальный Баш строгого режима : set -euo pipefail.
Пабло А
236

Один парень из SysOps однажды научил меня технике Трехпалого Когтя:

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

Эти функции * ОС NIX и оболочка устойчивы. Поместите их в начало вашего сценария (bash или иным образом),try() вашего утверждения и кода.

объяснение

(основываясь на комментариях летающих овец ).

  • yell: напечатать имя скрипта и все аргументы для stderr:
    • $0 путь к сценарию;
    • $* все аргументы.
    • >&2означает >перенаправление стандартного вывода на & pipe2 . труба1 была бы stdoutсама собой.
  • dieделает то же самое yell, но выходит с состоянием выхода, отличным от 0 , что означает «сбой».
  • tryиспользует ||(логическое значение OR), которое оценивает правую сторону только в случае сбоя левой.
    • $@это все аргументы опять, но разные .
c.gutierrez
источник
3
Я довольно новичок в сценариях Unix. Не могли бы вы объяснить, как выполняются вышеуказанные функции? Я вижу новый синтаксис, с которым я не знаком. Спасибо.
kaizenCoder
15
кричать : $0это путь к сценарию. $*все аргументы. >&2означает « >перенаправить стандартный поток на &канал 2». труба 1 была бы самой stdout. поэтому yell префиксирует все аргументы с именем скрипта и печатает в stderr. die делает то же самое, что и yell , но выходит со статусом выхода не равным 0, что означает «сбой». try использует логическое значение or ||, которое оценивает правую сторону только в том случае, если левая сторона не вышла из строя. $@это все аргументы опять, но разные . надеюсь, что все объясняет
летающие овцы
1
Я изменил это, die() { yell "$1"; exit $2; }чтобы вы могли передать сообщение и код выхода с помощью die "divide by zero" 115.
Марк Лаката
3
Я вижу, как бы я использовал yellи die. Однако tryне так уж и много. Можете ли вы привести пример его использования?
кшеной
2
Хм, а как вы используете их в сценарии? Я не понимаю, что вы подразумеваете под "try () ваше утверждение и код на".
TheJavaGuy-Иван Милосавлевич
33

Если вы будете вызывать скрипт с помощью source, вы можете использовать return <x>где <x>будет статус завершения скрипта (используйте ненулевое значение для ошибки или false). Но если вы вызываете исполняемый скрипт (т. Е. Непосредственно с его именем файла), оператор return приведет к жалобе (сообщение об ошибке «return: может только« вернуться »из функции или скрипта с источником»).

Если exit <x>вместо этого используется, когда сценарий вызывается source, это приведет к выходу из оболочки, которая запустила сценарий, но исполняемый сценарий просто завершится, как и ожидалось.

Чтобы обработать любой случай в одном и том же сценарии, вы можете использовать

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

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

Примечание: <x>предполагается, что это просто число.

kavadias
источник
У меня не работает в скрипте с возвратом / выходом внутри функции, т. Е. Возможно ли выйти внутри функции без существующей оболочки, но без того, чтобы вызывающая функция заботилась о правильной проверке кода возврата функции ?
Январь
@jan То, что вызывающая сторона делает (или не делает) с возвращаемыми значениями, полностью ортогонально (т.е. не зависит от) того, как вы возвращаетесь из функции (... без выхода из оболочки, независимо от того, какой вызов). Это в основном зависит от кода вызывающего абонента, который не является частью этого Q & A. Вы даже можете адаптировать возвращаемое значение функции к потребностям вызывающего, но этот ответ не ограничивает, каким может быть это возвращаемое значение ...
kavadias
11

Я часто включаю функцию run () для обработки ошибок. Каждый вызов, который я хочу сделать, передается этой функции, поэтому весь сценарий завершается, когда происходит сбой. Преимущество этого по сравнению с решением set -e состоит в том, что сценарий не завершает работу без вывода сообщений при сбое строки и может сказать вам, в чем проблема. В следующем примере 3-я строка не выполняется, потому что сценарий завершается при вызове false.

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)
Джозеф Шиди
Я просто реализовал это как часть сложного процесса развертывания, и он работал фантастически. Спасибо, и вот вам комментарий и отзыв.
fuzzygroup
Действительно потрясающая работа! Это самое простое и чистое решение, которое хорошо работает. Для меня я добавил это перед командой в цикле FOR, так как циклы FOR не будут забирать set -e option. Тогда команда, так как с аргументами, я использовал одиночные кавычки , чтобы избежать проблем Баш нравится так: 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.
Тони-Кафе
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]

Обратите внимание на пару круглых скобок, которые необходимы из-за приоритета оператора чередования. $?это специальная переменная, установленная для выхода из кода последней вызванной команды.

skalee
источник
1
если я это сделаю, то command -that --fails || exit $?это работает без скобок, что echo $[4/0]заставляет нас нуждаться в них?
Anentropic
2
@Anentropic @skalee Скобки не имеют ничего общего с приоритетом, а с обработкой исключений. Деление на ноль вызовет немедленный выход из оболочки с кодом 1. Без скобок (т. Е. Простых echo $[4/0] || exit $?) bash никогда не выполнит echo, не говоря уже о подчинении ||.
Боббого
1

У меня тот же вопрос, но я не могу его задать, потому что это будет дубликат.

Принятый ответ с использованием 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

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

Гироскоп Gearloose
источник