Выполнение exit
в подоболочке является одной ловушкой:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
Сценарий печатает 42, выходит из подоболочки с кодом возврата 1
и продолжает выполнение сценария. Даже замена вызова на echo $(CALC) || exit 1
не помогает, потому что код возврата echo
равен 0 независимо от кода возврата calc
. И calc
выполняется до echo
.
Еще большее недоумение сводит на нет эффект exit
его оборачивания во local
встроенный, как в следующем скрипте. Я наткнулся на проблему, когда написал функцию для проверки входного значения. Пример:
Я хочу создать файл с именем "year month day.log", т.е. 20141211.log
на сегодня. Дата вводится пользователем, который не может предоставить разумное значение. Поэтому в моей функции fname
я проверяю возвращаемое значение, date
чтобы проверить правильность ввода пользователя:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
Выглядит неплохо. Пусть сценарий будет назван s.sh
. Если пользователь вызывает скрипт с помощью ./s.sh "Thu Dec 11 20:45:49 CET 2014"
, файл 20141211.log
создается. Однако, если пользователь вводит ./s.sh "Thu hec 11 20:45:49 CET 2014"
, скрипт выводит:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
Строка fname…
говорит, что в подоболочке были обнаружены неверные входные данные. Но exit 1
в конце local …
строки никогда не срабатывает, потому что local
директива всегда возвращается 0
. Это потому, что local
выполняется после $(fname)
и, следовательно, перезаписывает свой код возврата. И из-за этого скрипт продолжается и запускается touch
с пустым параметром. Этот пример прост, но поведение bash может привести к путанице в реальном приложении. Я знаю, что настоящие программисты не используют местных жителей.
Чтобы было понятно: без local
, сценарий прерывается, как и ожидалось, когда вводится недопустимая дата.
Исправление состоит в том, чтобы разделить линию как
local FNAME
FNAME=$(fname "$1") || exit 1
Странное поведение соответствует документации local
на странице руководства bash: «Статус возврата равен 0, если local не используется вне функции, указано неверное имя или name является переменной только для чтения».
Хотя это и не ошибка, но я чувствую, что поведение bash нелогично. Я в курсе последовательности выполнения, тем local
не менее не следует маскировать нарушенное назначение.
Мой первоначальный ответ содержал некоторые неточности. После откровенного и глубокого обсуждения с mikeserv (спасибо за это) я пошел на их исправление.