Найти тему

Работа с linux. Exit codes.

Оглавление

Привет. Сегодня ещё один важный момент про работу с Linux - обработка кодов завершения процессов. У меня на данный момент стоит Ubuntu 18, все написанное дальше тестировалось на этой системе. Нюансы могут отличаться, но суть должна остаться неизменной. Код завершения (exit code) - число от 0 до 255, возвращаемое при завершении процесса в родительский процесс. Это число может быть интерпретировано программой и распознано как успех или провал.

Как правило, 0 код - успех, все остальные сигнализируют о разных причинах провала.

Как их отслеживать?

Получить код завершения предыдущего процесса можно с помощью команды `$?`

пример:

$ echo 'Hello, world!'
$ echo $?
0

0 - всё прошло успешно.

или так:

$ cat /some_file.txt
$ echo $?
1

1 - стандартный код для общих ошибок.

В данном случае ошибка - cat: /some_file.txt: Нет такого файла или каталога.

Коды завершения и конвейеры (pipelines)

У нас есть два оператора, позволяющие взаимодействовать с кодами завершения в конвейере - это `&&` и `||`

В теории `&&` можно было бы реализовать с помощью проверки условий if например так:

$ echo 'Hello,'
$ if [ $? -eq 0 ]; then
> echo 'World!'
> fi

Или использовать `if [ $? -ne 0 ]; then` для `||`, но для конвейера такое решение не годится.

`&&`действует как логические И. Оператор запускает следующую команду, если код завершения предыдущей 0.

$ echo 'Hello,' && echo 'World!'
Hello,
World!

`||` действует как логическое ИЛИ. Оператор запускает следующую команду, если предыдущая вернула код отличный от 0.

$ cat /some_file.txt || echo 'You don`t have that file!'
cat: /some_file.txt: Нет такого файла или каталога
You don`t have that file!

Хоть решение с if не подойдет для какого-нибудь сложного конвейера, с несколькими уровнями вложений, его удобно использовать для файлов-скриптов.

Коды завершения и .sh файлы

Мы конечно можем сплетать замысловатые цепочки команд в завораживающие конвейеры. Ощущаешь безграничную власть, когда можешь удержать такое в голове, но обычно это непонятно остальным и становится непонятно тебе спустя какое-то время (чаще всего в самый неподходящий момент).

do_cmd1 && do_cmd2 || do_cmd3 && do_cmd4 || do_cmd5 && do_cmd6 && do_cmd7 || do_cmdN

Поэтому обычно && || используются в составе .sh скриптов для улучшения читаемости и сокращения связанных команд. Рассмотрим пару вариантов, которые я использовал в работе.

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

Напишем тестовый файл:

#!/bin/bash
cat /smth.txt
echo Hello,

как мы уже помним, первая команда завершается ошибкой. Но последний код выхода будет положительным:

$ ./blog.sh && echo 'World!'
cat: /smth.txt: Нет такого файла или каталога
Hello,
World!

Если у вас есть несколько команд и важна правильность выполнения всего скрипта, но при этом нужно, чтобы скрипт проходил полностью (например в конце расположен код, удаляющий созданные вначале файлы вне зависимости от результатов тестов) можно поставить счетчик и возвращать 1 в случае, если он больше 0 уже после чистки.

#!/bin/bash
export COUNTER=0
echo 'Hello,' || COUNTER=$(($COUNTER+1))
cat /some_file.txt || COUNTER=$(($COUNTER+1))
if [ $COUNTER -ne 0 ]; then
exit 1
fi

Второй вариант - если нужно сразу прерывать скрипт при провале. Напишем небольшую проверочную функцию и будем запускать её после важных скриптов (так можно дополнительно кастомизировать сообщение). Получится что-то вроде обработчика исключений.

check()
{
if [ ${1} -ne 0 ]; then
echo "FAIL ${1} : ${2}"
exit ${1}
fi
}

Чтобы её применить нужно указать `check $? 'exception text'` после исполняемой команды. Первым аргументом вы передаете код завершения предыдущего процесса, а вторым ваш текст ошибки. Важно, что мы выходим с тем же кодом ошибки, который был передан от последнего проверяемого процесса. Отредактируем наш скрипт:

#!/bin/bash
check()
{
if [ ${1} -ne 0 ]; then
echo "FAIL ${1} : ${2}"
exit ${1}
fi
}
cat /some_file.txt
check $? "cat comand failed"
echo 'Hello,'
check $? "echo command can`t be failed"

_________________________________________________________________________________________

На этом все, надеюсь вы так же оцените удобство работы с командной строкой и этот инструмент вам поможет перейти на новый уровень)

-2