Коды возврата






В статье о сигналах мы рассматривали несложный механизм, позволяющий процессам в ОС реагировать на внешние события. Рассматривались способы отправки сигналов процессам при помощи kill, а также обработка поступающих сигналов в сценариях оболочки. Аналогично сигналам, коды возврата позволяют процессам взаимодействовать с вызвавшими их процессами. Эта тема частенько игнорируется пользователями, однако довольно! Сегодня мы поговорим о кодах возврата и работе с ними.


Чем являются коды возврата

Предлагаю начать наше знакомство с простой команды известной практически каждому — mv, которая перемещает файл из одного места файловой системы в другое и, возможно, переименовывает его. Как вы уже, наверное, заметили, при работе mv могут возникать ошибки в случае, если отсутствует исходный файл, или же возникли какие-то другие обстоятельства, помешавшие mv выполнить свою работу. Например:

$ mv ~/missing ~/missing2
mv: cannot stat `/home/ashep/missing': No such file or directory

Вы видите сообщение об ошибке. Очевидно, что команда не сработала. И в то же самое время за кулисами оболочки инициализируется переменная оболочки, содержащая так называемый «код возврата» последней выполненной команды. При желании мы можем получить значение этой переменной. Попробуйте:

$ mv ~/missing ~/missing2
mv: cannot stat `/home/ashep/missing': No such file or directory
$ echo $?
1
$ echo "Test me"
Test me
$ echo $?
0

Если команда выполняется без ошибок, то обычно её код возврата равен нулю. После выполнения команды оболочка автоматически устанавливает значение переменно $? равным этому коду. Если же команда завершится с ошибкой, то, как правило, её код возврата будет отличным от нуля. В примере выше мы сперва пытаемся переместить несуществующий файл при помощи команды mv. Естественно, мы получаем ошибку, о чём свидетельствует сообщение самой программы, а также код возврата равный единице. Затем мы выполняем команду echo, которая завершается успешно. Её код возврата равен нулю.

Давайте теперь обратимся к info-странице документации программы mv (info coreutils mv). В конце документа есть абзац, говорящий о том, что нулевой код возврата команды означает успешное выполнение, а ненулевой — об ошибке. Небогатый выбор, скажем честно, негде развернуться душе сисадмина!

Вот grep предлагает более широкий выбор средств диагностики результатов своей работы. Фрагмент из документации: «Обычно нулевой код возврата означает, что  искомые строки были найдены, и код равный единице в противном случае. Если же при запуске grep использовалась опция -q, --quiet или --silent, строки были найдены, но возникла какая-то ошибка, то возвращается код 2.»

Ниже приведён список определённых системных кодов возврата:

  • 1: общие ошибки;
  • 2: ошибки работы встроенных средств оболочки;
  • 126: невозможно вызвать запрошенную команду;
  • 127: команда не найдена;
  • 128: некорректный аргумент exit;
  • 128 + n: сигнал критической ошибки (например, kill -9 = 137);
  • 130: скрипт прерван по Ctrl+C.

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

Использование кодов возврата

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

#!/bin/bash
mkdir /usr
echo \$? = $?
if [ $? -ne 0 ] ; then
  echo "mkdir /usr failed: we have an exit code of $?"
  exit 1
fi
echo "made the requested directory. Why is '/' world writable?"
exit 0

Оказывается, есть нюанс при работе с переменной $?, который вызван выполнением команд, вроде echo. Взгляните на результат работы сценария:

$ ./test.sh
mkdir: /usr: File exists
$? = 1
made the requested directory. Why is '/' world writable?

Увидели в чём проблема? Код возврата сразу после вызова mkdir равен единице, и это логично, поскольку каталог /usr существует. Но когда мы проверяем значение переменной $? в конструкции if, оказывается, что её значение равно нулю! Почему так? Потому что в этот момент значение переменной $? содержит код возврата предыдущего вызова echo, а не команды mkdir.

Получившуюся проблему можно решить, например, так:

#!/bin/bash
mkdir /usr
error=$?
if [ $error -ne 0 ] ; then
  echo "mkdir /usr failed: we have an exit code of $error"
  exit 1
fi

Выше показан один из случаев, когда дублирование значения глобальной переменной в локальной имеет смысл. Таким образом, вы получаете возможность затем использовать сохранённое значение глобальной переменной там, где вам нужно, не заботясь о том, что её значение может измениться в результате каких-то событий.

Конечно же, обработка ошибок далеко не всегда связана лишь с выводом сообщений и завершением работы сценария. Рассмотрим сценарий, скачивающий файл с нескольких источников до тех пор, пока не будет успешно скачен:

alternates='
   http://www.example.com/test.pdf
   http://www.example2.com/test.pdf
   http://www.example3.com/test.pdf
   '
gotit=0
for file in $alternates
do
  wget $file
  if [ $? -ne 0 ]; then
    echo "Unable to get $file
  else
    gotit=1
    break
  fi
done

Сокрытие сообщений об ошибках

Теперь, когда вы знаете, как анализировать код возврата программы, вы можете заменять текст сообщений об ошибках программы на свой собственный. Это можно сделать при помощи оператора >&, который перенаправляет стандартный поток вывода и поток ошибок. Например, в нашем первом простом сценарии вывод команды mkdir можно перенаправить таким образом:

mkdir /usr >& /dev/null

Вместо >& можно с тем же успехом использовать &> или 2>&1. Конечно, если вы проверяете код возврата команды, то особого смысла в замене текста сообщений об ошибках нет.

По мотивам linuxjournal.com