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

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

Например, выполните следующие команды:

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

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

 cd foo rm a cd bar rm b 

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

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

 myfunc() { echo 'Error raised. Exiting!' } trap 'myfunc' ERR # file does not exist, causing error ls asd echo 123 

Обратите внимание, что ERR ловушка bash не подразумевает set -o errexit или set -e и не является POSIX.

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

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

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

Трудная часть состоит в том, что вы помещаете свои команды в if часть if then else , а не в часть then или else . Напомним, что синтаксис оператора if

  если список ;  затем список ;  [ список elif ;  затем список ;  ] ... [ список остальных ;  ] fi
    ↑↑↑↑ 

set -e сообщает оболочке, что если cmd 1 ( cd foo ) терпит неудачу, он не должен продолжаться и выполнять cmd 2 ( rm a ) и т. Д. Вниз по строке. Если это произошло с командой на самом внешнем уровне скрипта оболочки, оболочка выйдет. Однако, поскольку cmd 1 · cmd 2 · cmd 3 · cmd 4 является (составным) списком, следующий за if , отказ любой из этих четырех команд просто приводит к сбою всего списка, что приводит к выполнению предложения else . Если все четыре команды успешны, выполняется предложение then .

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

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

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

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

Например, эта цепочка сломается и напечатает «что-то пошло не так», если какая-либо из предыдущих команд сломалась (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 означает успех.

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

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

Пример:

 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