Лучшая практика использования $? в баш?

10

Когда я прочитал этот ответ о $? другой вопрос приходит на ум.

Есть ли лучшая практика для использования $? в баш?


Давайте приведем пример:

У нас есть линейный скрипт, и я хотел бы знать, что все команды были выполнены нормально. Считаете ли вы, что можно вызывать небольшую функцию (давайте назовем ее «did_it_work»), чтобы проверить код ошибки и сломать, если это не так.

#!/bin/bash 

function did_it_work {
    code=$1
    if [ "$code" -ne "0" ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

dir=some/path

mkdir -p $dir
did_it_work $? 

cd $dir
did_it_work $? 

run_some_command
did_it_work $? 

Этот подход, конечно, означает, что я должен вручную решить проблему, если она есть, и перезапустить скрипт.

Как вы думаете, это хорошая идея или есть другие лучшие практики для этого?

/Спасибо

Johan
источник

Ответы:

15

Один из распространенных способов:

die() {
    IFS=' ' # make sure "$*" is joined with spaces

    # output the arguments if any on stderr:
    [ "$#" -eq 0 ] || printf '%s\n' "$*" 1>&2
    exit 1
}

тогда вы используете это так:

mkdir -p some/path || die "mkdir failed with status $?"

Или, если вы хотите, чтобы он включал статус выхода, вы можете изменить его на:

die() {
    last_exit_status=$?
    IFS=' '
    printf '%s\n' "FATAL ERROR: $* (status $last_exit_status)" 1>&2
    exit 1
}

и затем использовать его немного проще:

mkdir -p some/path || die "mkdir failed"

Когда это не удастся, mkdirскорее всего уже будет выдано сообщение об ошибке, так что второе может рассматриваться как избыточное, и вы можете просто сделать:

mkdir -p some/path || exit   # with the same (failing) exit status as mkdir's
mkdir -p some/path || exit 1 # with exit status 1 always

(или используйте первый вариант dieвыше без аргумента)

На случай, если вы раньше не видели command1 || command2, он запускается command1, а если command1не получается, он запускается command2.

Таким образом, вы можете прочитать это как «сделать каталог или умереть».

Ваш пример будет выглядеть так:

mkdir -p some/path || die "mkdir failed"
cd some/path || die "cd failed"
some_command || die "some_command failed"

Или вы можете выровнять diesдальше вправо так, чтобы основной код был более очевидным.

mkdir -p some/path         || die "mkdir failed"
cd some/path               || die "cd failed"
some_command               || die "some_command failed"

Или в следующей строке, когда командные строки длинные:

mkdir -p some/path ||
  die "mkdir failed"

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Кроме того, если вы собираетесь использовать имя some/pathнесколько раз, сохраните его в переменной, чтобы вам не приходилось вводить его снова, и вы можете легко изменить его, если потребуется. И при передаче переменных аргументов командам, обязательно используйте --разделитель параметров, чтобы аргумент не принимался как параметр, если он начинается с -.

dir=some/path
mkdir -p -- "$dir"         || die "Cannot make $dir"
cd -P -- "$dir"            || die "Cannot cd to $dir"
some_command               || die "Cannot run some_command"
Mikel
источник
9

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

#!/bin/bash
function try {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "$1 did not work: exit status $code"
        exit 1
    fi
}

try mkdir -p some/path
try cd some/path
try run_some_command

Если вам на самом деле не нужно регистрировать код ошибки, а просто, была ли команда выполнена успешно или нет, вы можете сократить ее try()следующим образом:

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}
Уоррен Янг
источник
Вы также можете использовать код в этом формате. <pre> function try {
BillThor
Если вы используете эту функцию встроенным и не хотите возвращаться с истинной стороны, вы можете заменить return $?на :встроенную.
BillThor
8

Если вы действительно хотите получить exitсообщение об ошибке и используете Bash, вам также следует подумать об этом set -e. От help set:

-e Выйти немедленно, если команда выходит с ненулевым статусом.

Это, конечно, не дает вам гибкости функции did_it_work (), но это простой способ убедиться, что ваш bash-скрипт останавливается на ошибке без добавления большого количества вызовов к вашей новой функции.

Стивен Д
источник
set -eявляется полезным. Есть некоторые команды, которые возвращают ненулевое значение при нормальных обстоятельствах (например, diff). Когда я использую set -e в скрипте, где я ожидаю ненулевой возврат, я делаю command || true.
Шон Дж. Гофф
2
Кроме того, даже если вы используете set -e, вы можете установить «обработчик исключений», чтобы перехватывать все ошибки trap did_it_work EXIT.
Жиль "ТАК - перестань быть злым"
1
Это часть POSIX, а не особенность Bash. Используйте его, но помните о некоторых подводных камнях mywiki.wooledge.org/BashFAQ/105
kmkaplan
@ ShawnJ.Goff Я предпочитаю делать command && true. Как то возвращаемое значение не изменяется.
kmkaplan
@kmkaplan Ваш последний комментарий не имеет никакого смысла для меня. Цель command || trueсостоит в том, чтобы предотвратить set -eвыход из скрипта, если commandвозвращается ненулевой код завершения . Это меняет код выхода, потому что нам нужно. Единственное, что command && trueнужно сделать - это запустить true(вернуть нулевой код выхода), если `команда выполнена успешно (вернула нулевой код выхода) - это полный запрет.
tripleee