Bash pipes and exit problem PDF Печать E-mail
Автор: Павел Алексеев aka Pahan-Hubbitus   
12.12.2010 21:19

В bash использование команд в конвеере (piping) приводит к тому что они выполняются в подоболочке (subshell), что может приводить к неожиданным и весьма серьезным последствиям. Особенно в случае если вы проверяете ошибку и хотите выйти из скрипта по этой команде.

 

У меня есть маленькая вспомогательная функция оьбщего назначения - assert (родилась по прочтении http://tldp.org/LDP/abs/html/debugging.html ну и слегка модифицированная):

#################################################################
# $1 - assertion to test
# $2 - (opt) - text of error to add in output if assertion failed
function assert ()# If condition false, exit from script
{                 #+ with appropriate error message.
E_ASSERT_FAILED=99

if [ ! $1 ]; then
echo "Assertion failed:  \"$1\". $2"
echo "Caller (line, file): $( caller )"
exit $E_ASSERT_FAILED
fi
}
#################################################################

Все выглядит просто прекрасно, и я стал ее использовать.

Но, как оказалось, здесь тоже есть свои подводные камни.

Странным образом она перестала работать, и заметил я это совершенно случайно заметоив сообщение в логе. Перестала работать странным образом - ошибка выводится, но скрипт продолжает работу! Начав копать, нашел весьма интересное поведение:

echo 'Before function'
#assert # Выходит нормально
assert | tee -a LOG # Не выходит никак! Выполнение продолжается!
echo 'After function'

Я потратил много времени чтобы разобраться что к чему. Если кратко - все дело в сабшелле!

Это может быть совершенно не очевидно, но оказалось это упомянуто в мане (побробнее объяснение можно прочесть здесь: http://steve-parker.org/sh/functions.shtml ). Оказалось что нету стандартного пути остановить вызывающий скрипт в этом случае (вне зависимости return или exit используется в функции: http://stackoverflow.com/questions/4419952/difference-between-return-and-exit-in-bash-functionshttp://stackoverflow.com/questions/1816824/exit-entire-program-from-a-function-call-in-shell. Исключение из этого правила я нашел способ "убить себя" - "kill $$"  ( http://www.unix.com/shell-programming-scripting/138182-functions-exit-kill-bash.html ). Это может сработать в конкретных ситуациях, но также имеет некоторые обратные стороны, такие как невозможность вернуть код возврата, можно убить не тот скрипт который намеревался когда он включен в другой ну и подобные. Другой путь сделать такую проверку - на том же уровне вызова фвно проверив специальную переменную $PIPESTATUS (дополнительные примеры и подробности: http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-06/1079.html). Чтобы также выходить с нужным кодом возврата я написал еще одну маленькую функцию:

# $1 - pipestatus code
function pipe_check_exit(){
[ $1 -gt 0 ] && exit $1
}

Использовать вместе:

assert '1 -gt 2' | tee LOG
pipe_check_exit ${PIPESTATUS: -1}
Share/Save/Bookmark
 

Добавить комментарий


Защитный код
Обновить