Как выйти из сценария оболочки, если одна его часть не работает?

51

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

n=0
until [ $n -ge 5 ]
do
  gksu *command* && break
  n=$[$n+1]
  sleep 3
Weylyn
источник

Ответы:

88

Одним из подходов будет добавление set -eв начало вашего сценария. Это значит (с help set):

  -e  Exit immediately if a command exits with a non-zero status.

Таким образом, если какая-либо из ваших команд завершится неудачно, скрипт завершится.

В качестве альтернативы, вы можете добавить явные exitоператоры в возможных точках отказа:

command || exit 1
Тердон
источник
5
Я (и bash wiki ) предпочел бы, чтобы люди задумывались о правильной обработке ошибок, а не об использовании (сломанной по конструкции IMO) set -eфункции. Это действительно не применимо здесь, хотя. ОП хочет выйти из сценария после 5 неудачных попыток выполнить команду.
Стефан Шазелас
1
@ StéphaneChazelas Я не буду спорить с тобой о том, сломался ли он, я уверен, что ты прав. Однако ОП спросил: «Как я могу написать сценарий оболочки, который завершается, если одна его часть выходит из строя?», Что заставляет вас думать о выходе из 5 ошибок?
Тердон
потому что я не могу думать ни о каком другом способе интерпретации вопроса.
Стефан Шазелас
1
@ StéphaneChazelas ты вполне можешь быть прав. Я истолковал это буквально: как может завершиться весь сценарий, если какая-то его часть потерпит неудачу. И set -eэто единственный способ, который я знаю, чтобы сделать это.
Тердон
В этом сценарий snipet, команды , которые могут вызвать set -eэто sleep( breakбудучи специальной встроенной команды вызовет сценарий выхода на неудачу в большинстве оболочек, команд ifили слева от &&не влияет set -e, n=...может потерпеть неудачу , если nэто только для чтения, но потом это также вышло бы из сценария set -e), так что интерпретация кажется маловероятной. Я согласен, что вопрос плохо сформулирован.
Стефан Шазелас
17

Вы можете выйти из скрипта в любом месте, используя ключевое слово exit. Вы также можете указать код завершения, чтобы указать другим программам, что или как ваш сценарий вышел из строя, например, exit 1и exit 2т. Д. (Условно, код завершения 0 предназначен для успеха, а все, что больше 0, означает сбой; однако, также по соглашению, выход коды выше 127 зарезервированы для аварийного завершения (например, по сигналу)).

Общая конструкция для выхода из строя

if [ failure condition ]; then
    exit n
fi

с подходящими failure conditionи n. Но в определенных сценариях вы можете действовать иначе. Теперь для вашего случая я интерпретирую ваш вопрос, что если какой-либо из пяти вызовов gksuпотерпел неудачу, то вы собираетесь выйти. Одним из способов является использование такой функции

function try_command {
    for i in 1 2 3 4 5 ; do
        if gksu command ; then
            return 0
        fi
    fi
    exit 1
}

а затем вызвать цикл с помощью try_command.

Существуют (более) продвинутые или сложные способы решения вашего вопроса. Однако приведенное выше решение более доступно для начинающих, чем, скажем, решение Стефана.

countermode
источник
10
attempt=0
until gksu command; do
  attempt=$((attempt + 1))
  if [ "$attempt" -gt 5 ]; then
    exit 1
  fi
done

exitвыходит из сценария, если он не вызывается в подоболочке. Если эта часть скрипта в субоболочке, например , потому что это в пределах (...)или $(...)или части трубы линии, то это будет только выйти из этого подоболочки .

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

Например, здесь с 2 вложенными уровнями подоболочек:

(
  life=hard
  output=$(
    echo blah
    [ "$life" = easy ] || exit 1 # exit subshell
    echo blih not run
  ) || exit # if the subshell exits with a non-zero exit status,
            # exit as well with the same exit status

  echo not run either
) || exit # if the subshell exits with a non-zero exit status,
          # exit as well with the same exit status

Это может стать сложнее, если подоболочка является частью конвейера. bashимеет специальный $PIPESTATUSмассив, похожий на zsh«s $pipestatusодин , который может помочь вам здесь:

{
   echo foo
   exit 1
   echo bar
} | wc -c
subshell_ret=${PIPESTATUS[0]}
if [ "$subshell_ret" -ne 0 ]; then
  exit "$subshell_ret"
fi
Стефан Шазелас
источник
3

Ловушка выполнит действие при получении сигнала.

trap "echo EXIT;  exit" 0
trap "echo HUP;   exit" 1
trap "echo CTL-C; exit" 2
trap "echo QUIT;  exit" 3
trap "echo ERR;   exit" ERR
n=0
until [ $n -ge 5 ]
do
  n=$[$n+1]
  echo $n
  sleep 3
done

Запустите это и дайте ему выйти нормально. Ловит сигнал 0.

EXIT

Запустите его снова и прервите с помощью ^ C. Он захватывает как сигнал 2, так и сигнал 0.

CTL-C
EXIT

Ненулевой статус выхода будет ловушкой ERR

ERR
EXIT
RobP
источник