Скрипт выхода из оболочки на основе кода выхода процесса


378

У меня есть сценарий оболочки, который выполняет ряд команд. Как заставить скрипт сценария завершиться, если любая из команд завершается с ненулевым кодом выхода?


3
Я ответил, что вы используете bash, но если это совсем другая оболочка, можете ли вы указать это в своем посте?
Мартин У

Жесткий метод: проверять значение $?после каждой команды. Простой способ: поставить set -eили #!/bin/bash -eв верхней части вашего сценария Bash.
mwfearnley

Ответы:


495

После каждой команды код выхода можно найти в $?переменной, так что вы получите что-то вроде:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

Вы должны быть осторожны с конвейерными командами, так как $?только вы получите код возврата последнего элемента в конвейере, поэтому в коде:

ls -al file.ext | sed 's/^/xx: /"

не вернет код ошибки, если файл не существует (поскольку sedчасть конвейера фактически работает, возвращая 0).

bashОболочка фактически обеспечивает массив , который может помочь в этом случае, что быть PIPESTATUS. Этот массив имеет один элемент для каждого из компонентов конвейера, к которому вы можете обращаться по отдельности, например ${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

Обратите внимание, что это результат falseкоманды, а не всего конвейера. Вы также можете получить весь список для обработки по своему усмотрению:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

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

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

Это проходит каждый из PIPESTATUSэлементов по очереди, сохраняя его, rcесли он был больше, чем предыдущее rcзначение.


39
Та же особенность только в одной строке переносимого кода: ls -al file.ext || exit $?([[]] не переносима)
2010 г.,

19
MarcH, я думаю, вы обнаружите, что [[ ]]это довольно переносимо bash, и это вопрос, которым помечен вопрос :-) Как ни странно, lsон не работает, command.comтак что он тоже не переносим, ​​я знаю, но это тот же аргумент, что и вы. подарок.
paxdiablo

39
Я знаю, что это древнее, но следует отметить, что вы можете получить код выхода команд в трубе через массив PIPESTATUS(т . ${PIPESTATUS[0]}${PIPESTATUS[1]}${PIPESTATUS[*]}
Е.

11
Следует подчеркнуть, что элегантные и идиоматические сценарии оболочки очень редко требуют $?непосредственного изучения . Вы обычно хотите что-то вроде того, if ls -al file.ext; then : nothing; else exit $?; fiчто, как, например, @MarcH говорит, эквивалентно, ls -al file.ext || exit $?но если предложения thenили elseявляются несколько более сложными, это более удобно для обслуживания.
tripleee

9
[[ $rc != 0 ]]даст вам 0: not foundили 1: not foundошибку. Это должно быть изменено на [ $rc -ne 0 ]. Также rc=$?может быть удален и просто использован [ $? -ne 0 ].
CurtisLeeBolin

223

Если вы хотите работать с $ ?, вам нужно будет проверять его после каждой команды, так как $? обновляется после выхода каждой команды. Это означает, что если вы выполните конвейер, вы получите только код завершения последнего процесса в конвейере.

Другой подход заключается в том, чтобы сделать это:

set -e
set -o pipefail

Если вы поместите это в начало сценария оболочки, похоже, bash позаботится об этом за вас. Как отмечалось в предыдущем постере, «set -e» приведет к выходу bash с ошибкой в ​​любой простой команде. "set -o pipefail" приведет к завершению работы bash с ошибкой и для любой команды в конвейере.

Смотрите здесь или здесь для более подробного обсуждения этой проблемы. Вот раздел руководства bash по встроенному набору.


6
Это действительно должен быть главный ответ: сделать это намного, намного проще, чем использовать PIPESTATUSи проверять коды выхода везде.
Candu

2
#!/bin/bash -eэто единственный способ запустить скрипт оболочки. Вы всегда можете использовать такие вещи, как, например, foo || handle_error $?если вам нужно проверить статусы выхода.
Дэвис Херринг

53

« set -e», вероятно, самый простой способ сделать это. Просто поместите это перед любыми командами в вашей программе.


6
@SwaroopCH set -eваш скрипт будет прерван , если какая-либо команда в вашем скрипте завершится со статусом ошибки, и вы не обработали эту ошибку.
Андрей

2
set -eна 100% эквивалентно тому, set -o errexitчто в отличие от первого можно искать. Ищите opengroup + errexit для официальной документации.
марта

30

Если вы просто вызовите команду exit в bash без параметров, она вернет код завершения последней команды. В сочетании с ИЛИ bash должен вызывать выход только в случае неудачи предыдущей команды. Но я не проверял это.

command1 || Выход;
command2 || Выход;

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



21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. Как я могу получить код выхода cmd1вcmd1|cmd2

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

    cmd | head -1

    Вы можете наблюдать состояние выхода 141 (или 269 с ksh93) cmd1, но это потому, что он cmdбыл прерван сигналом SIGPIPE при head -1завершении после прочтения одной строки.

    Знать состояние выхода элементов трубопровода cmd1 | cmd2 | cmd3

    а. с зш:

    Коды выхода представлены в специальном массиве pipestatus. cmd1код выхода в $pipestatus[1], cmd3код выхода в $pipestatus[3], так что $?всегда совпадает с $pipestatus[-1].

    б. с баш:

    Коды выхода представлены в PIPESTATUSспециальном массиве. cmd1код выхода в ${PIPESTATUS[0]}, cmd3код выхода в ${PIPESTATUS[2]}, так что $?всегда совпадает с ${PIPESTATUS: -1}.

    ...

    Для более подробной информации смотрите следующую ссылку .


19

для Баш:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

19
Вероятно, не следует «выходить из 0», поскольку это указывает на успех.
Нобар

3
exit_code = $ ?; echo "скрипт прерван из-за ошибок"; exit $
exit_code

1
@ HAL9001 у тебя есть доказательства этого? В документации IBM сказано иначе .
Патрик Джеймс Макдугл

11

В bash это легко, просто свяжите их вместе с &&:

command1 && command2 && command3

Вы также можете использовать вложенную конструкцию if:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi

+1 Это было самое простое решение, которое я искал. Кроме того, вы также можете написать, if (! command)если вы ожидаете ненулевой код ошибки от команды.
Берчи

это для последовательных команд ... что делать, если я хочу запустить эти 3 параллельно и убить всех, если какой-либо из них завершится неудачно?
Василе Сурду

4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit

2
Там редко причина для использования $*; "$@"вместо этого используйте для сохранения пробелов и подстановочных знаков.
Дэвис Херринг
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.