Bash: выход и очистка при ошибке

В своих сценариях Bash я хотел бы убедиться, что сценарий завершает работу, как только возникает ошибка. (Например, чтобы избежать ошибочного rm -f * после неудачного cd some_directory.) По этой причине я всегда использую флаг -e для bash.

Теперь я хотел бы также выполнить некоторый код очистки в некоторых из моих сценариев. Из этой записи в блоге я собрал

#!/bin/bash

cd invalid_directory
echo ':('

function clean_up {
  echo "> clean_up"
  exit 0
}
trap clean_up EXIT

Результат, который я получаю,

./test.sh: line 3: cd: invalid_directory: No such file or directory
:(
> clean_up

поэтому он делает то, что рекламируется. Однако при использовании -e для bash я получаю только

./test.sh: line 3: cd: invalid_directory: No such file or directory

поэтому сценарий завершается без вызова clean_up.

Как заставить скрипт bash завершать работу при всех ошибках и каждый раз вызывать скрипт очистки?


person Nico Schlömer    schedule 31.03.2016    source источник
comment
Лучше заниматься проверкой ошибок самостоятельно, чем полагаться на set -e. cd invalid_directory || clean_up.   -  person chepner    schedule 31.03.2016
comment
@NicoSchlömer Вы пробовали trap clean_up ERR?   -  person Biffen    schedule 31.03.2016
comment
@chepner Не могли бы вы расширить это? Какая польза?   -  person Biffen    schedule 31.03.2016
comment
@chepner Скрипты, которые у меня есть, обычно намного больше, чем в приведенном выше примере. Обработка всего этого вручную слишком утомительна и подвержена ошибкам для меня.   -  person Nico Schlömer    schedule 31.03.2016
comment
@Biffen Спасибо за подсказку. К сожалению, ERR не помогает.   -  person Nico Schlömer    schedule 31.03.2016
comment
@NicoSchlömer Это работает для меня. Не могли бы вы опубликовать MCVE?   -  person Biffen    schedule 31.03.2016
comment
@Biffen См. mywiki.wooledge.org/BashFAQ/105.   -  person chepner    schedule 31.03.2016
comment
Ваш текущий подход также подвержен ошибкам. Используйте хотя бы предсказуемый подход.   -  person chepner    schedule 31.03.2016
comment
Возможный дубликат Bash: запустить команду перед выходом скрипта?   -  person Calimo    schedule 25.04.2017


Ответы (1)


Вы никогда не достигаете команды trap; ваша оболочка завершает работу до того, как ловушка настроена.

set -e
clean_up () {
    ARG=$?
    echo "> clean_up"
    exit $ARG
} 
trap clean_up EXIT
cd invalid_directory
echo "Shouldn't reach this"

Однако лучше обработать ошибки самостоятельно. Часто бывает необходимо изменить свое поведение в зависимости от точной причины, по которой почему завершается ваш скрипт, что будет сложнее сделать, если вы используете один обработчик для всех выходов ( даже если вы ограничите свою ловушку ERR вместо EXIT).

cd invalid_directory || { echo "cd to invalid_directory failed" >&2; exit 1; }
echo "Shouldn't reach this"

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

person chepner    schedule 31.03.2016
comment
Похоже, вы (ОП) намеревались cd invalid_directory || exit. Просто проверяйте, иначе каждый раз вы будете получать Не следует достигать этого. - person David C. Rankin; 31.03.2016
comment
Это то, что я бы сделал на самом деле (см. мой комментарий к вопросу). Я просто указываю, почему обработчик выхода не выполняется. Однако я обновлю этот подход. - person chepner; 31.03.2016
comment
Я так и думал, но я просто чесал затылок, глядя на строку Не должен достигать этой. - person David C. Rankin; 31.03.2016
comment
О верно; Я оставил подразумеваемым, что код запускался с set -e :) - person chepner; 31.03.2016
comment
Спасибо за отличный ответ @chepner! Знаете ли вы, можно ли установить код выхода равным 0 для EXIT и 1 для ERR? - person Nico Schlömer; 31.03.2016
comment
Используйте отдельные ловушки. trap normal_exit EXIT; trap err_exit ERR. Однако в любой ловушке вы можете сохранить значение $? в начале вашей ловушки на случай, если оно понадобится вам после того, как любые последующие команды изменят его значение. - person chepner; 31.03.2016
comment
@chepner Спасибо за совет! Две отдельные ловушки на самом деле не нужны, поскольку ловушка EXIT в конечном итоге вызывается, несмотря ни на что. Если бы вы изменили свой пример на exit $ARG с ARG=$? в качестве первой строки clean_up, это точно соответствовало бы моему варианту использования. - person Nico Schlömer; 31.03.2016
comment
Ах, я ошибочно предположил, что обработчик EXIT будет пропущен, если сработает обработчик ERR. - person chepner; 31.03.2016