С чего начать Первые шаги трек · 15 ступеней
Linux Labs

Коды завершения: $?, && и ||

8 мин Ubuntu 24.04 · bash 5.2

Код завершения — число от 0 до 255, которым команда сообщает итог: 0 — успех, другое число — сбой или ответ «нет».

Шпаргалка

8 команд Версия для печати
  • ls /nope; echo $? 2 — код последней команды
  • grep -q root /etc/passwd && echo есть вторая — после кода 0
  • mkdir /srv/app || echo "нет" >&2 вторая — после кода не 0
  • if grep -q '^backup:' /etc/passwd; then echo есть; fi
  • rc=$? сохранить код, пока не затёрт
  • exit 1 выйти из скрипта с кодом 1
  • false | true; echo "${PIPESTATUS[@]}" 1 0 — коды каждого звена
  • set -o pipefail упало звено — упал конвейер

Обзор

Каждая команда, закончив работу, отдаёт оболочке код завершения — целое число от 0 до 255. Ноль значит «успех», любое другое число — сбой или ответ «нет». На экран код не выводится: его хранит переменная $? до следующей команды.

text
student@ubuntu:~$ ls /nope
ls: cannot access '/nope': No such file or directory
student@ubuntu:~$ echo $?
2

Текст ошибки пишется для человека в поток ошибок. Программы читают только код: if, &&, ||, while, systemd, cron и мониторинг.

Код Что значит
0 успех
1 общий сбой; у grep — «не нашёл», у diff — «файлы различаются»
2 ошибка вызова: ls не нашёл путь, grep не открыл файл
124 timeout прервал команду по времени
126 файл найден, но не запускается: нет права x
127 команда не найдена
128+N процесс убит сигналом N: 130 — Ctrl+C, 137 — kill -9, 143 — kill

Код 128+N значит, что процесс завершил сигнал с номером N.

Смысл 1, 2 и других малых чисел у каждой программы свой — он описан в разделе EXIT STATUS её man. У curl, например, 6 — «имя не найдено», 7 — «не удалось подключиться», 28 — «вышло время», 22 — ответ HTTP 400 и выше при ключе -f.

&& и ||

a && b запускает b, только если a вернула 0. a || b запускает b, только если a вернула не 0. Код всей строки — код последней выполненной команды. ! перед командой меняет успех на сбой и обратно: ! true даёт 1.

Код скрипта

Скрипт завершается с кодом последней выполненной команды, если раньше не встретил exit N. exit без числа отдаёт код предыдущей команды. Код берётся по модулю 256: exit 256 даёт 0, exit 300 — 44.

Код конвейера

Код конвейера — код последнего звена: false | true вернёт 0. Коды всех звеньев лежат в массиве PIPESTATUS. После set -o pipefail конвейер возвращает код самого правого упавшего звена, а 0 — только если успешны все.

Рецепты

6

Проверить команду в if

bash
if grep -q '^backup:' /etc/passwd; then
  echo "пользователь backup есть"
fi

if сам смотрит на код команды, $? здесь не нужен. Запись cmd; if [ $? -ne 0 ] работает, но ломается, стоит вставить между ними echo.

Сохранить код, пока его не затёрли

bash
curl -s -o /dev/null --max-time 5 http://127.0.0.1:8080/
rc=$?
echo "curl вернул $rc"

$? перезаписывает любая команда — echo, [ ], присваивание с подстановкой. Нужен код дважды — сохрани его в переменную сразу.

Выйти с сообщением об ошибке

bash
die() { echo "$*" >&2; exit 1; }
[ -r "$conf" ] || die "нет файла $conf"
cd /srv/app || die "нет каталога /srv/app"

Сообщение — в поток ошибок (>&2), код — не ноль: так скрипт понятен и человеку, и тому, кто его вызвал.

Код curl и ответ HTTP

Код curl отвечает на вопрос, состоялся ли обмен с сервером. Ответ 503 — тоже состоявшийся обмен, поэтому код 0. Ключ -f делает сбоем ответы от 400 и выше (код 22), а 204 и 302 для него — успех. Так отвечал учебный сервер:

Ответ сервера curl -s curl -sf curl -s -w '%{http_code}'
200 0 0 200
204, 302 0 0 204, 302
404, 503 0 22 404, 503
порт закрыт 7 7 000

Нужен точный код ответа — печатай его ключом -w '%{http_code}' и сравнивай в проверке. С -o и без -s curl рисует индикатор загрузки в поток ошибок.

Свои коды для разных сбоев

bash
[ $# -eq 1 ] || { echo "Usage: check.sh URL" >&2; exit 2; }
curl -sf -o /dev/null "$1" || exit 1

Принято: 1 — проверка не прошла, 2 — скрипт вызвали неправильно. Числа больше 125 не бери: 126, 127 и 128+N оболочка уже занимает своим смыслом.

Код каждого звена конвейера

text
student@ubuntu:~$ false | true; echo "rc=$? ps=${PIPESTATUS[*]}"
rc=0 ps=1 0
student@ubuntu:~$ set -o pipefail; false | true; echo "rc=$?"
rc=1

Неполадки

7

Скрипт пишет OK, а служба лежит

Проверка смотрит на код не той команды:

text
student@ubuntu:~$ curl -s http://127.0.0.1:1/; echo OK; echo "rc=$?"
OK
rc=0

echo OK выполнился после curl и записал в $? свой ноль. Проверяй саму команду: if curl -sf …; then echo OK; fi.

Строка в середине упала, а скрипт вернул 0

Код скрипта — код последней команды. Так выглядит журнал службы (journalctl -u clean.service -o cat), чей скрипт упал на второй строке, а последняя команда (find) прошла:

text
/usr/local/bin/clean.sh: line 2: report-tool: command not found
clean.service: Deactivated successfully.

Ставь || exit 1 после важных команд или set -e в начале скрипта (см. bash-scripting).

Скрипт всё сделал, а вернул 1

Та же причина с другой стороны. Последняя строка вида [ "$DEBUG" = yes ] && echo "готово" при пустой DEBUG вернула 1 — и скрипт вместе с ней, хотя работа выполнена. Служба после такого скрипта — failed. Заверши скрипт явным exit 0 или перепиши строку через if.

Код 127

command not found: команды нет в PATH. Руками скрипт работает, а из cron или таймера падает — там свой короткий PATH (см. окружение таймера). Пиши полный путь к программе.

Код 126

Файл найден, но не запускается: bash: ./check.sh: Permission denied. Нет права x (chmod +x check.sh) или файловая система смонтирована с noexec.

Код 130, 137 или 143

Команду прервал сигнал: 130 — Ctrl+C, 143 — kill или systemctl stop, 137 — kill -9, timeout -s KILL или нехватка памяти. Ищи, кто его послал, в журнале: sudo journalctl -k | grep -i oom.

grep обрывает скрипт с set -e

grep без совпадений возвращает 1. Для set -e и pipefail это сбой, и скрипт выходит. Если «не нашёл» — нормальный исход, пиши grep … || true или проверяй его в if.

Опасно

2

a && b || c вместо if

c выполнится и тогда, когда упала b:

text
student@ubuntu:~$ true && false || echo "c выполнилась"
c выполнилась

Где важно «либо одно, либо другое» (удалить или сообщить) — пиши if a; then b; else c; fi.

Число ошибок в коде

exit $errors при 256 ошибках вернёт 0 — успех. Код не счётчик: при любой ошибке выходи с 1, а число пиши в вывод.

Термины

1
код завершения exit status
Результат выполнения команды: ноль обычно означает успех, ненулевой код требует интерпретации. Ещё встречается в: Docker, stress-ng, Bash-скрипты: запуск, переменные, аргументы, dmesg и ещё 5

См. также

  • bash-scripting — set -euo pipefail, условия и циклы в скриптах.
  • test-conditions — проверки [ ] и [[ ]], которые тоже отвечают кодом.
  • redirection — поток ошибок, >&2 и конвейеры.
  • curl — запросы для проверки служб.
  • kill — сигналы, от которых код 128+N.
  • systemctl — код службы в systemctl status.
  • shellcheck — найдёт $? после лишней команды и cd без || exit.
Загрузка…