Выполнение 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 (спасибо за это) я пошел на их исправление.