Коды возврата
$?, цепочки && и ||, set -e/-u/-o pipefail, exit и trap — как скрипт узнаёт об ошибках
Каждая команда, завершаясь, возвращает число от 0 до 255. 0 — успех, любое другое — ошибка или «нет». На этом коде держатся if, &&, ||, while и все проверки в скриптах.
Синтаксис
команда; echo $? # код последней команды
команда1 && команда2 # вторая — только если первая успешна
команда1 || команда2 # вторая — только если первая упала
exit N # выйти из скрипта с кодом N
return N # выйти из функции с кодом N
Частые коды
| Код | Что обычно значит |
|---|---|
0 | успех |
1 | общая ошибка; у grep — «ничего не найдено», у diff — «есть различия» |
2 | неправильное использование: неверная опция, синтаксическая ошибка; у grep — ошибка чтения |
124 | timeout прервал команду по времени |
126 | файл найден, но не исполняемый (нет x) |
127 | команда не найдена (опечатка, нет в PATH) |
128+N | процесс убит сигналом N |
130 | 128+2: прерван Ctrl+C (SIGINT) |
137 | 128+9: убит SIGKILL — часто это OOM killer |
143 | 128+15: завершён SIGTERM |
255 | exit -1 или код вне диапазона; у ssh — ошибка соединения |
Код берётся по модулю 256: exit 300 даёт 44.
Операторы и переменные
| Запись | Что делает |
|---|---|
$? | код последней команды; перезаписывается каждой командой, включая echo и [[ ]] |
a && b | b, если a вернула 0 |
a || b | b, если a вернула не 0 |
a && b || c | c выполнится, если упадёт a или b — это не if/else |
! команда | инвертировать код: 0 → 1, не-0 → 0 |
${PIPESTATUS[@]} | коды всех частей последнего конвейера |
rc=$? | сохранить код сразу, пока его не перезаписали |
команда || true | «ошибка не важна» — код всегда 0 |
exit без числа | выйти с кодом последней команды |
Опции set
| Опция | Что делает |
|---|---|
set -e (-o errexit) | выйти из скрипта, как только команда вернёт не 0 (с исключениями, см. ниже) |
set -u (-o nounset) | обращение к незаданной переменной — ошибка и выход |
set -o pipefail | конвейер падает, если упала любая его часть, а не только последняя |
set -x (-o xtrace) | печатать каждую команду перед выполнением (с префиксом +) — отладка |
set -E (-o errtrace) | trap на ERR срабатывает и внутри функций |
set +e, set +x … | выключить опцию |
set -euo pipefail | частое начало «строгого» скрипта |
set -o | показать состояние всех опций |
set -e не срабатывает, если команда стоит: в условии if/while/until, слева от && или ||, после !, внутри конвейера (кроме последней части, если нет pipefail).
trap
| Запись | Что делает |
|---|---|
trap 'команды' EXIT | выполнить при любом выходе из скрипта: обычном, по exit, по set -e |
trap 'команды' ERR | выполнить, когда команда вернула не 0 (там же, где сработал бы set -e) |
trap 'команды' INT TERM | перехватить Ctrl+C и kill |
trap '' INT | игнорировать сигнал |
trap - INT | вернуть обработку по умолчанию |
trap -p | показать установленные обработчики |
SIGKILL (9) и SIGSTOP перехватить нельзя.
Примеры
# узнать код
ls /nope 2>/dev/null; echo $? # 2
grep -q zzz /etc/passwd; echo $? # 1 — не найдено
# выполнить, только если предыдущее удалось
mkdir -p /tmp/build && cd /tmp/build && echo "готово"
# запасной вариант при ошибке
cd /some/dir || exit 1
# сохранить код и разобрать его
some_command
rc=$?
if (( rc != 0 )); then echo "упало с кодом $rc" >&2; fi
# проверить успех и сохранить вывод
if ! out=$(ls /nope 2>&1); then
echo "ошибка: $out" >&2
fi
# строгий режим в начале скрипта
#!/bin/bash
set -euo pipefail
# при set -u — значение по умолчанию для необязательной переменной
echo "${DEBUG:-0}"
# удалить временный файл при любом выходе
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
# сообщить, где упал скрипт
set -eE
trap 'echo "ошибка в строке $LINENO" >&2' ERR
# код возврата при убийстве сигналом
sleep 100 & kill $!; wait $!; echo $? # 143
Подводные камни
$?живёт одну команду.cmd; echo "статус"; echo $?покажет кодecho. Сохраняйrc=$?сразу.a && b || c— не if/else. Еслиaуспешна, аbупала, выполнится иc. Для настоящего ветвления пишиif.set -eне ловит ошибки в условиях. Функция, вызванная какf || echo fail, выполняется целиком без errexit: упавшие команды внутри неё не остановят выполнение.local x=$(cmd)иexport x=$(cmd)прячут ошибку: код — отlocal/export, то есть 0. Разделяй:local x; x=$(cmd).((i++))приi=0возвращает 1, иset -eзавершит скрипт. Пиши((i+=1))илиi=$((i+1)).- Без
pipefailошибка в середине конвейера теряется:false | true— успех. exitв подоболочке выходит только из неё.( exit 1 )илиcmd | while …; do exit 1; doneскрипт не завершат.- trap на сигнал без
exitне завершает скрипт.trap 'echo stop' TERMперехватитkill, и скрипт продолжит работу. Добавляйexitв обработчик.