Как я могу получить bash для выхода из-за сбоя обратной связи таким же образом, как pipefail?


55

Поэтому мне нравится укреплять свои bash-скрипты везде, где я могу (и когда не могу делегировать такие языки, как Python / Ruby), чтобы ошибки не остались без ошибок.

В этом ключе у меня есть strict.sh, который содержит такие вещи, как:

set -e
set -u
set -o pipefail

И источник его в других сценариях. Однако пока pipefail подхватит:

false | echo it kept going | true

Не подберут

echo The output is '`false; echo something else`' 

Выход будет

Выход «»

False возвращает ненулевой статус и no-stdout. В трубе это не удалось бы, но здесь ошибка не обнаружена. Когда это на самом деле вычисление, сохраненное в переменной для последующего использования, и значение установлено как пустое, это может привести к более поздним проблемам.

Итак, есть ли способ заставить bash рассматривать ненулевой код возврата внутри обратной черты как достаточную причину для выхода?

Ответы:


51

Точный язык, используемый в спецификации Single UNIX для описания значенияset -e :

Когда эта опция включена , если простая команда завершается неудачно по какой-либо из причин, перечисленных в « Последствиях ошибок оболочки», или возвращает значение состояния выхода> 0 и не является [условной или отрицательной командой], оболочка должна немедленно завершиться.

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

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

Обратите внимание, что это будет работать так, как задумано, только в случае замены одной команды, поскольку учитывается только состояние последней замены. Например, следующая команда является успешной (как в соответствии со стандартом, так и в каждой реализации, которую я видел):

a=$(false)$(echo foo)

Еще один случай , чтобы наблюдать за это явные Подоболочки : (somecommand). Согласно приведенной выше интерпретации, подоболочка может возвращать ненулевой статус, но, поскольку это не простая команда в родительской оболочке, родительская оболочка должна продолжаться. Фактически, все снаряды, о которых я знаю, возвращают родителей в этот момент. Хотя это полезно во многих случаях, например, (cd /some/dir && somecommand)когда круглые скобки используются для сохранения локальной операции, такой как изменение текущего каталога, это нарушает спецификацию, если set -eона отключена в подоболочке, или если подоболочка возвращает ненулевой статус таким образом, что не будет прервать его, например, при использовании !истинной команды. Например, все команды ash, bash, pdksh, ksh93 и zsh выходят без отображения fooв следующих примерах:

set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"

Пока ни одна простая команда не провалилась, пока set -eона действовала!

Третий проблемный случай - это элементы в нетривиальном конвейере . На практике все оболочки игнорируют отказы элементов конвейера, кроме последнего, и демонстрируют одно из двух поведений относительно последнего элемента конвейера:

  • ATT ksh и zsh, которые выполняют последний элемент конвейера в родительской оболочке, работают как обычно: если простая команда завершается неудачно в последнем элементе конвейера, оболочка, выполняющая эту команду, которая оказывается родительской оболочкой, выходы.
  • Другие оболочки приближают поведение, завершаясь, если последний элемент конвейера возвращает ненулевое состояние.

Как и раньше, отключение set -eили использование отрицания в последнем элементе конвейера заставляет его возвращать ненулевой статус таким образом, который не должен завершать оболочку; затем выйдут другие оболочки кроме ATT ksh и zsh.

pipefailОпция Bash приводит к немедленному завершению конвейера, set -eесли какой-либо из его элементов возвращает ненулевое состояние.

Обратите внимание, что в качестве дополнительного осложнения bash отключается set -eв подоболочках, если он не находится в режиме POSIX ( set -o posixили не находится POSIXLY_CORRECTв среде, когда запускается bash).

Все это показывает, что спецификация POSIX, к сожалению, плохо определяет -eопцию. К счастью, существующие оболочки в основном соответствуют своему поведению.


Спасибо за это. У меня был опыт, когда некоторые ошибки, обнаруженные в более поздней версии bash, игнорировались в более ранних версиях с помощью set -e. Мое намерение здесь состоит в том, чтобы укрепить сценарии до такой степени, что любое необработанное условие возврата / сбоя ошибки приведет к завершению сценария. Это в унаследованной системе, которая, как известно, создает полные выходные файлы и «0» счастливый код завершения после получаса хаоса с неправильным env - если вы не смотрели вывод как ястреб (и не все ошибки находятся на stderr, некоторые на stdout, а некоторые на / dev / null'd), вы просто не знаете.
Дэнни Стейпл

«Например, все из ash, bash, pdksh, ksh93 и zsh выходят без отображения foo на следующих примерах»: BusyBox ash ведет себя не так, он отображает вывод в соответствии со спецификацией. (Протестировано с BusyBox 1.19.4.) Также не завершается с set -e; (cd /nonexisting).
dubiousjim

27

(Отвечая на мой вопрос, потому что я нашел решение) Одно из решений - всегда назначать это промежуточной переменной. Таким образом, код возврата ( $?) устанавливается.

Так

ABC=`exit 1`
echo $?

Будет выводить 1(или вместо этого выходить, если set -eприсутствует), однако:

echo `exit 1`
echo $?

Будет выводить 0после пустой строки. Код возврата echo (или другой команды, которая выполнялась с выводом backtick) заменит код возврата 0.

Я все еще открыт для решений, которые не требуют промежуточной переменной, но это помогает мне в этом.


24

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

Тем не менее, один крайний случай все еще может озадачить вас ложными отрицаниями (т. Е. Команда не выполняется, но ошибка не всплывает), localобъявление переменной:

local myvar=$(subcommand)будет всегда возвращаться 0!

bash(1) указывает на это:

   local [option] [name[=value] ...]
          ... The return status is 0 unless local is used outside a function,
          an invalid name is supplied, or name is a readonly variable.

Вот простой тестовый пример:

#!/bin/bash

function test1() {
  data1=$(false) # undeclared variable
  echo 'data1=$(false):' "$?"
  local data2=$(false) # declaring and assigning in one go
  echo 'local data2=$(false):' "$?"
  local data3
  data3=$(false) # assigning a declared variable
  echo 'local data3; data3=$(false):' "$?"
}

test1

Выход:

data1=$(false): 1
local data2=$(false): 0
local data3; data3=$(false): 1

спасибо, непоследовательность VAR=...и local VAR=...правда меня обескуражили!
Джонни

13

Как уже говорили другие, localвсегда будет возвращать 0. Решение состоит в том, чтобы сначала объявить переменную:

function testcase()
{
    local MYRESULT

    MYRESULT=$(false)
    if (( $? != 0 )); then
        echo "False returned false!"
        return 1
    fi

    return 0
}

Выход:

$ testcase
False returned false!
$ 

4

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

set -e
x=$(set -e; false; true)
echo "this will never be shown"

1

Интересный момент!

Я никогда не сталкивался с этим, потому что я не являюсь другом set -e(вместо этого я предпочитаю trap ... ERR), но уже проверял это: trap ... ERRтакже не улавливаю ошибки внутри $(...)(или старомодные кавычки).

Я думаю, что проблема (как это часто бывает) в том, что здесь вызывается подоболочка и -eявно означает текущую оболочку.

Единственное другое решение, которое пришло на ум в этот момент, это использовать чтение:

 ls -l ghost_under_bed | read name

Это броски ERRи с -eоболочкой будут прекращены. Единственная проблема: это работает только для команд с одной строкой вывода (или вы пропускаете что-то, соединяющее строки).


1
Не уверен насчет уловки чтения, попытка привести к тому, что переменная не будет привязана. Я думаю, что это может быть из-за того, что другая сторона трубы фактически является подоболочкой, имя не будет доступно.
Дэнни Стейпл
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.