Вихід із сценарію оболонки на основі коду виходу з процесу


378

У мене є скрипт оболонки, який виконує ряд команд. Як змусити вийти зі скрипту оболонки, якщо будь-яка команда завершиться з ненульовим кодом виходу?


3
Я відповів, припускаючи, що ви використовуєте bash, але якщо це зовсім інша оболонка, чи можете ви вказати у своїй публікації?
Martin W

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

Відповіді:


494

Після кожної команди код виходу можна знайти у $?змінній, щоб у вас вийшло щось на зразок:

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 $?([[]] не є портативним)
березня 10.10

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пропозиції є дещо складнішими, вони є більш ретельними.
трійка

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 з помилкою в будь-якій команді в конвеєрі.

Дивіться тут чи тут, щоб трохи більше обговорити цю проблему. Ось базовий розділ керівництва на вбудованому наборі.


6
Це дійсно повинна бути головною відповіддю: зробити це набагато, набагато простіше, ніж це використовувати PIPESTATUSта перевіряти вихідні коди скрізь.
candu

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

53

" set -e" це, мабуть, найпростіший спосіб зробити це. Просто поставте це перед будь-якими командами у вашій програмі.


6
@SwaroopCH set -eваш сценарій скасує , якщо будь-яка команда у вашому скрипті вийде зі статусом помилки і ви не впоралися з цією помилкою.
Андрій

2
set -eце 100% еквівалент, до set -o errexitякого на відміну від попереднього можна шукати. Шукайте операційну групу + errexit для офіційної документації.
березня 1818 року

30

Якщо ви просто зателефонуєте на вихід у bash без параметрів, він поверне код виходу останньої команди. У поєднанні з АБО bash повинен викликати вихід, лише якщо попередня команда не вдалася. Але я цього не перевіряв.

команда1 || вихід;
команда2 || вихід;

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}.

    ...

    Детальніше дивіться за наступним посиланням .


19

для 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;
}

19
Напевно, не слід «виходити з 0», оскільки це свідчить про успіх.
nobar

3
exit_code = $ ?; echo "скрипт перерваний через помилки"; exit $ exit_code
RaSergiy

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

11

У bash це просто, просто зв’яжіть їх разом із &&:

command1 && command2 && command3

Ви також можете використовувати вкладені if construct:

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.