Есть ли способ, чтобы функция в моем скрипте bash автоматически запускалась при любой ошибке команды?

12

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

Например, возьмите эти команды:

cd foo || myfunc
rm a || myfunc
cd bar || myfunc
rm b || myfunc


Есть ли способ, которым я могу как-то сигнализировать оболочке перед выполнением этих команд, что она должна вызвать myfunc, если какая-либо из них не удалась, чтобы вместо этого я мог написать что-то более чистое, например

cd foo
rm a
cd bar
rm b
тестовое задание
источник

Ответы:

13

Вы можете использовать bash trap ERR, чтобы заставить ваш скрипт завершиться, если какая-либо команда вернет статус больше нуля и выполнит вашу функцию при выходе.

Что-то вроде:

myfunc() {
  echo 'Error raised. Exiting!'
}

trap 'myfunc' ERR

# file does not exist, causing error
ls asd
echo 123

Обратите внимание, что bash trap ERR неявный set -o errexitили set -eне является POSIX.

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

cuonglm
источник
1

(Возможно) более простая вариация принятого ответа:

  1. Используется set -e для сбоя одной команды, чтобы прервать выполнение списка.
  2. Просто перечислите свои команды.
  3. Используйте if- then- elseзаявление для выполнения вашей обработки ошибок команды (ы). Этот последний кусок немного сложнее. Смотреть:
установить -е
если
    cmd 1                         # например, cd foo
     cmd 2                         # например, rm a
     cmd 3                         # например, cd bar
     cmd 4                         # например, rm b
тогда
    набор + е
    команды, чтобы сделать в случае успеха (если есть)
еще
    набор + е
    MyFunc
    другие команды делать в случае отказа (если таковые имеются) 
фи

Хитрость в том , что вы кладете ваши команды в в ifчасти от if- then- else, а не thenчасть или elseчасти. Напомним , что синтаксис ifзаявления является

если  список ; затем  список ; [  список элифов ; затем  список ; ] ... [еще  список ; ] Фи 
   ↑↑↑↑
Эта set -eкоманда сообщает оболочке, что если ( ) произойдет сбой, она не должна продолжаться, выполнять ( ) и так далее в конце. Если это произойдет с командой на внешнем уровне сценария оболочки, оболочка закроется. Однако, поскольку · · · является (составным) списком, следующим за , сбой любой из этих четырех команд просто приводит к сбою всего списка - что приводит к выполнению предложения. Если все четыре команды выполнены успешно, предложение выполняется.cmd1cd foocmd2rm acmd1cmd2cmd3cmd4ifelsethen

В любом случае, первое, что вы должны сделать, это, вероятно, отключить (отключить) eопцию, выполнив set +e. В противном случае сценарий может вылететь из воды в случае myfuncсбоя команды.

set -eкак указано и описано в спецификации POSIX .

G-Man говорит: «Восстанови Монику»
источник
0

Если взять слово « каждая команда зависит от каждой предыдущей команды. Если какая-либо команда терпит неудачу, весь сценарий должен потерпеть неудачу », я думаю, вам не нужны никакие специальные функции для обработки ошибок.

Все, что вам нужно, это связать ваши команды с &&оператором и ||оператором, который делает именно то, что вы написали.

Например, эта цепочка прервется и выведет «что-то пошло не так», если какая-либо из предыдущих команд прервалась (bash читает слева направо)

cd foo && rm a && cd bar && rm b || echo "something went wrong"

Реальный пример (я создал dir foo, файл a, dir bar и файл b только для реальной демонстрации):

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm bb || echo "something is wrong"
rm: cannot remove 'bb': No such file or directory
something is wrong #mind the error in the last command

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm aa && cd bar && rm b || echo "something is wrong"
rm: cannot remove 'aa': No such file or directory
something is wrong #mind the error in second command in the row

И, наконец, если все команды были выполнены успешно (код выхода 0), скрипт просто продолжается:

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm b || echo "something is wrong"
gv@debian:/home/gv/Desktop/PythonTests/foo/bar$ 
# mind that the error message is not printed since all commands were successful.

Важно помнить, что при использовании && следующая команда выполняется, если предыдущая команда вышла с кодом 0, что для bash означает успех.

Если какая-либо команда в цепочке не работает, то команда / скрипт / все, что следует || будет выполнен.

И просто для записи: если вам нужно выполнить различные действия в зависимости от команды, которая сломалась, вы также можете сделать это с помощью классического сценария, отслеживая значение $?которого сообщает код завершения точно предыдущей команды (возвращает ноль, если команда выполнена успешно) или другое положительное число, если команда не выполнена)

Пример:

for comm in {"cd foo","rm a","cd bbar","rm b"};do  #mind the error in third command
eval $comm
    if [[ $? -ne 0 ]];then 
        echo "something is wrong in command $comm"
        break
    else 
    echo "command $comm executed succesful"
    fi
done

Выход:

command cd foo executed succesfull
command rm a executed succesfull
bash: cd: bbar: No such file or directory
something is wrong in command cd bbar

Совет: Вы можете подавить сообщение "bash: cd: bbar: Нет такого файла ...", применив eval $comm 2>/dev/null

Георгий Василиу
источник