- Работа с linux. Exit codes.
- Как их отслеживать?
- Коды завершения и конвейеры (pipelines)
- Коды завершения и .sh файлы
- Как использовать коды завершения в Bash-скриптах
- Что такое коды завершения
- Что происходит, когда коды завершения не определены
- Как использовать коды завершения в Bash-скриптах
- Проверяем коды завершения
- Создаём собственный код завершения
- Как использовать коды завершения в командной строке
- Дополнительные коды завершения
Работа с linux. Exit codes.
Привет. Сегодня ещё один важный момент про работу с Linux — обработка кодов завершения процессов. У меня на данный момент стоит Ubuntu 18, все написанное дальше тестировалось на этой системе. Нюансы могут отличаться, но суть должна остаться неизменной. Код завершения (exit code) — число от 0 до 255, возвращаемое при завершении процесса в родительский процесс. Это число может быть интерпретировано программой и распознано как успех или провал.
Как правило, 0 код — успех, все остальные сигнализируют о разных причинах провала.
Как их отслеживать?
Получить код завершения предыдущего процесса можно с помощью команды `$?`
1 — стандартный код для общих ошибок.
В данном случае ошибка — cat: /some_file.txt: Нет такого файла или каталога.
Коды завершения и конвейеры (pipelines)
У нас есть два оператора, позволяющие взаимодействовать с кодами завершения в конвейере — это `&&` и `||`
В теории `&&` можно было бы реализовать с помощью проверки условий if например так:
Или использовать `if [ $? -ne 0 ]; then` для `||`, но для конвейера такое решение не годится.
`&&`действует как логические И. Оператор запускает следующую команду, если код завершения предыдущей 0.
`||` действует как логическое ИЛИ. Оператор запускает следующую команду, если предыдущая вернула код отличный от 0.
Хоть решение с if не подойдет для какого-нибудь сложного конвейера, с несколькими уровнями вложений, его удобно использовать для файлов-скриптов.
Коды завершения и .sh файлы
Мы конечно можем сплетать замысловатые цепочки команд в завораживающие конвейеры. Ощущаешь безграничную власть, когда можешь удержать такое в голове, но обычно это непонятно остальным и становится непонятно тебе спустя какое-то время (чаще всего в самый неподходящий момент).
Поэтому обычно && || используются в составе .sh скриптов для улучшения читаемости и сокращения связанных команд. Рассмотрим пару вариантов, которые я использовал в работе.
Нюанс в том, что .sh скрипт движется дальше и командой $? вы получите только код завершения последней команды. Поэтому если команда, которая может вернуть ошибку где-то в середине, то код затеряется и дальше его видно не будет. Если такой скрипт участвует в конвейере, то следующая команда получит ложный сигнал.
как мы уже помним, первая команда завершается ошибкой. Но последний код выхода будет положительным:
Если у вас есть несколько команд и важна правильность выполнения всего скрипта, но при этом нужно, чтобы скрипт проходил полностью (например в конце расположен код, удаляющий созданные вначале файлы вне зависимости от результатов тестов) можно поставить счетчик и возвращать 1 в случае, если он больше 0 уже после чистки.
Второй вариант — если нужно сразу прерывать скрипт при провале. Напишем небольшую проверочную функцию и будем запускать её после важных скриптов (так можно дополнительно кастомизировать сообщение). Получится что-то вроде обработчика исключений.
Чтобы её применить нужно указать `check $? ‘exception text’` после исполняемой команды. Первым аргументом вы передаете код завершения предыдущего процесса, а вторым ваш текст ошибки. Важно, что мы выходим с тем же кодом ошибки, который был передан от последнего проверяемого процесса. Отредактируем наш скрипт:
На этом все, надеюсь вы так же оцените удобство работы с командной строкой и этот инструмент вам поможет перейти на новый уровень)
Как использовать коды завершения в Bash-скриптах
Инструменты автоматизации и мониторинга удобны тем, что разработчик может взять готовые скрипты, при необходимости адаптировать и использовать в своём проекте. Можно заметить, что в некоторых скриптах используются коды завершения (exit codes), а в других нет. О коде завершения легко забыть, но это очень полезный инструмент. Особенно важно использовать его в скриптах командной строки.
Что такое коды завершения
В Linux и других Unix-подобных операционных системах программы во время завершения могут передавать значение родительскому процессу. Это значение называется кодом завершения или состоянием завершения. В POSIX по соглашению действует стандарт: программа передаёт 0 при успешном исполнении и 1 или большее число при неудачном исполнении.
Почему это важно? Если смотреть на коды завершения в контексте скриптов для командной строки, ответ очевиден. Любой полезный Bash-скрипт неизбежно будет использоваться в других скриптах или его обернут в однострочник Bash. Это особенно актуально при использовании инструментов автоматизации типа SaltStack или инструментов мониторинга типа Nagios. Эти программы исполняют скрипт и проверяют статус завершения, чтобы определить, было ли исполнение успешным.
Кроме того, даже если вы не определяете коды завершения, они всё равно есть в ваших скриптах. Но без корректного определения кодов выхода можно столкнуться с проблемами: ложными сообщениями об успешном исполнении, которые могут повлиять на работу скрипта.
Что происходит, когда коды завершения не определены
В Linux любой код, запущенный в командной строке, имеет код завершения. Если код завершения не определён, Bash-скрипты используют код выхода последней запущенной команды. Чтобы лучше понять суть, обратите внимание на пример.
#!/bin/bash touch /root/test echo created file
Этот скрипт запускает команды touch и echo . Если запустить этот скрипт без прав суперпользователя, команда touch не выполнится. В этот момент мы хотели бы получить информацию об ошибке с помощью соответствующего кода завершения. Чтобы проверить код выхода, достаточно ввести в командную строку специальную переменную $? . Она печатает код возврата последней запущенной команды.
Как видно, после запуска команды ./tmp.sh получаем код завершения 0 . Этот код говорит об успешном выполнении команды, хотя на самом деле команда не выполнилась. Скрипт из примера выше исполняет две команды: touch и echo . Поскольку код завершения не определён, получаем код выхода последней запущенной команды. Это команда echo , которая успешно выполнилась.
Если убрать из скрипта команду echo , можно получить код завершения команды touch .
Поскольку touch в данном случае — последняя запущенная команда, и она не выполнилась, получаем код возврата 1 .
Как использовать коды завершения в Bash-скриптах
Удаление из скрипта команды echo позволило нам получить код завершения. Что делать, если нужно сделать разные действия в случае успешного и неуспешного выполнения команды touch ? Речь идёт о печати stdout в случае успеха и stderr в случае неуспеха.
Проверяем коды завершения
Выше мы пользовались специальной переменной $? , чтобы получить код завершения скрипта. Также с помощью этой переменной можно проверить, выполнилась ли команда touch успешно.
#!/bin/bash touch /root/test 2> /dev/null if [ $? -eq 0 ] then echo "Successfully created file" else echo "Could not create file" >&2 fi
После рефакторинга скрипта получаем такое поведение:
- Если команда touch выполняется с кодом 0 , скрипт с помощью echo сообщает об успешно созданном файле.
- Если команда touch выполняется с другим кодом, скрипт сообщает, что не смог создать файл.
Любой код завершения кроме 0 значит неудачную попытку создать файл. Скрипт с помощью echo отправляет сообщение о неудаче в stderr .
Создаём собственный код завершения
Наш скрипт уже сообщает об ошибке, если команда touch выполняется с ошибкой. Но в случае успешного выполнения команды мы всё также получаем код 0 .
Поскольку скрипт завершился с ошибкой, было бы не очень хорошей идеей передавать код успешного завершения в другую программу, которая использует этот скрипт. Чтобы добавить собственный код завершения, можно воспользоваться командой exit .
#!/bin/bash touch /root/test 2> /dev/null if [ $? -eq 0 ] then echo "Successfully created file" exit 0 else echo "Could not create file" >&2 exit 1 fi
Теперь в случае успешного выполнения команды touch скрипт с помощью echo сообщает об успехе и завершается с кодом 0 . В противном случае скрипт печатает сообщение об ошибке при попытке создать файл и завершается с кодом 1 .
Как использовать коды завершения в командной строке
Скрипт уже умеет сообщать пользователям и программам об успешном или неуспешном выполнении. Теперь его можно использовать с другими инструментами администрирования или однострочниками командной строки.
В примере выше && используется для обозначения «и», а || для обозначения «или». В данном случае команда выполняет скрипт ./tmp.sh , а затем выполняет echo «bam» , если код завершения 0 . Если код завершения 1 , выполняется следующая команда в круглых скобках. Как видно, в скобках для группировки команд снова используются && и || .
Скрипт использует коды завершения, чтобы понять, была ли команда успешно выполнена. Если коды завершения используются некорректно, пользователь скрипта может получить неожиданные результаты при неудачном выполнении команды.
Дополнительные коды завершения
Команда exit принимает числа от 0 до 255 . В большинстве случаев можно обойтись кодами 0 и 1 . Однако есть зарезервированные коды, которые обозначают конкретные ошибки. Список зарезервированных кодов можно посмотреть в документации.
Адаптированный перевод статьи Understanding Exit Codes and how to use them in bash scripts by Benjamin Cane. Мнение администрации Хекслета может не совпадать с мнением автора оригинальной публикации.