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

290

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

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

9 ответов

373
Лучший ответ

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

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.

  • 38
    Та же функция в одной строке переносимого кода: ls -al file.ext || exit $? ([[]] не является переносимым)
  • 18
    MarcH, я думаю, вы обнаружите, что [[ ]] довольно переносим в bash , и это вопрос, который помечен в вопросе :-) Как ни странно, ls не работает в command.com поэтому он также не переносим, по-моему, я знаю, , но это тот же вид аргумента, который вы приводите.
Показать ещё 7 комментариев
190

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

Другой подход заключается в следующем:

set -e
set -o pipefail

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

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

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

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

  • 22
    Что оно делает? Любая ссылка на документы, пожалуйста?
  • 6
    @SwaroopCH set -e ваш скрипт будет прерван, если какая-либо команда в вашем скрипте завершится со статусом ошибки, и вы не обработали эту ошибку.
Показать ещё 1 комментарий
24

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

command1 || exit;
command2 || exit;

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

    а. с zsh:

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

    б. с bash:

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

    ...

    Подробнее см. ссылку .

18
[ $? -eq 0 ] || exit $?; # exit for none-zero return code
  • 2
    Разве это не должно быть exit $? (без паренов)?
17

для bash:

# 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;
}
  • 18
    Вероятно, не следует «выходить из 0», поскольку это указывает на успех.
  • 3
    exit_code = $ ?; echo "скрипт прерван из-за ошибок"; выйти из $ exit_code
Показать ещё 1 комментарий
8

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

command1 && command2 && command3

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

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi
  • 0
    +1 Это было самое простое решение, которое я искал. Кроме того, вы также можете написать if (! command) если вы ожидаете ненулевой код ошибки от команды.
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
  • 1
    Редко есть причина использовать $* ; вместо этого используйте "$@" для сохранения пробелов и подстановочных знаков.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню