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

Я сделал сценарий Perl, который делает что-то подобное тому, о чем Вы говорите:

http://pastebin.com/U7mFHZU7

В основном это просто пересекает каталог, вычисляя SHA1sum файлов в нем, хешируя его и соединяющих соответствий. Это пригодилось на многих, многих случаях.

55
21.10.2011, 13:42
7 ответов

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

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

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

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

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

a=$(false)$(echo foo)

Другой случай для наблюдения за является явными подоболочками: (somecommand). Согласно интерпретации выше, подоболочка может возвратить ненулевое состояние, но так как это не простая команда в родительской оболочке, родительская оболочка должна продолжиться. На самом деле все оболочки, о которых я знаю, действительно делают родительский возврат в этой точке. В то время как это полезно во многих случаях такой как (cd /some/dir && somecommand) где круглые скобки используются для хранения операции, такой как изменение текущего каталога локальной, это нарушает спецификацию если set -e выключен в подоболочке, или если бы подоболочка возвращает ненулевое состояние способом, которое не завершило бы ее, такие как использование ! на истинной команде. Например, весь пепел, удар, 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 затем выйдут.

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

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

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

52
27.01.2020, 19:33
  • 1
    Спасибо за это. Опыт, который я имел, состоял в том, что некоторые ошибки, зафиксированные в более поздней версии удара, были проигнорированы в более ранних версиях с набором-e. Мое намерение здесь состоит в том, чтобы укрепить сценарии до такой степени, что любой необработанный ошибочный возврат/состояние отказа заставит сценарий выходить. Это находится в унаследованной системе, которая, как было известно, произвела выходные файлы мусора и "0" счастливый код выхода после половины часа хаоса с неправильным ENV - если Вы не наблюдали вывода как ястреб (и не все ошибки находятся на stderr, некоторые находятся на stdout, и некоторые части являются/dev/null'd), Вы просто не знаете. –  Danny Staple 26.10.2011, 02:11
  • 2
    , "Например, весь пепел, удар, pdksh, ksh93 и zsh выходят, не отображая нечто на следующих примерах": пепел BusyBox не ведет себя тот путь, он отображает вывод согласно спецификации. (Протестированный с BusyBox 1.19.4.), И при этом это не выходит с set -e; (cd /nonexisting). –  dubiousjim 13.01.2013, 23:39

Решение

Если вы используете Bash 4.4 или более позднюю версию , вы можете использовать параметр shoptinherit_errexit, чтобы сделать именно это. Вы можете проверить совместимость из Bash, используя echo $BASH_VERSION.

Вот шебанг , который вы использовали бы, если бы был установлен Bash 4.4 или более поздней версии и стоял перед /binв вашем$PATH:

#!/usr/bin/env -S bash -euET -o pipefail -O inherit_errexit

-Sпредназначен для того, чтобы уговорить Linux envпринять более одного аргумента для bash, как любезно указал @UVV и далее объяснил на StackOverflow .


Фон

inherit_errexitявляется опцией shopt, а остальные аргументы являются опциями set. В большинстве современных итераций их можно передать непосредственно в bashпри вызове оболочки.

Давайте рассмотрим варианты, которые вы уже использовали:

  • -u/-o nounset, как двусмысленно намекает название, запрещает разыменование переменных, которые не были установлены; например, $IJUSTMADETHISUP. В основном это помогает защититься от опечаток или недосмотров при копировании -вставки из Stack Overflow
  • -e/ -o errexitвыполняет некоторые из того, что вы запрашиваете :при прямом вызове†, команды оболочки с ненулевыми кодами состояния вызовут завершение работы оболочки с ошибкой. То есть все, что предшествует строке скрипта оболочки, должно «работать правильно», чтобы эта строка могла быть выполнена.
  • -o pipefailнеобходимо, чтобы распространить эту гарантию на команды, вывод которых перенаправляется с помощью конвейера ввода/вывода |.

† т. е. не из подоболочки(...)

Теперь о параметрах, которые я добавил:

  • -O inherit_errexitдополнительно расширяет эту функциональность (выхода при ненулевом коде состояния )для команд, вызываемых из подоболочек. Это закрывает важную лазейку, поскольку подоболочки используются для подстановки команд $(...),`...`и подстановки процессов <(...), >(...),и оба они встречаются во многих сценариях оболочки в дикой природе. ‡
  • Опции -E/-o errtraceи -T/-o functraceпредназначены для сравнительно редкого случая, когда вы используете trapдля выполнения действия, когда оболочка получает сигнал. Эти две опции расширяют обработчики сигналов до внутренних тел функций оболочки для сигналов ERRи сигналов DEBUG/RETURNсоответственно.

‡ Это всего лишь одна из причин, по которой для подстановки команд следует предпочесть синтаксис $(...), поскольку круглые скобки указывают на то, что вы входите в подоболочку. Он также бывает вложенным.


См. также

17
20.08.2021, 13:32

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

Так

ABC=`exit 1`
echo $?

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

echo `exit 1`
echo $?

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

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

27
27.01.2020, 19:33

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

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

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

Только другое решение, которое пришло на ум в данный момент, будет состоять в том, чтобы использовать чтение:

 ls -l ghost_under_bed | read name

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

1
27.01.2020, 19:33
  • 1
    Не уверенный в приеме чтения, делая попытку его приводят к переменной не быть связанным. Я думаю, что это может быть то, потому что другая сторона канала является эффективно подоболочкой, имя не будет доступно. –  Danny Staple 21.10.2011, 18:09

Поскольку 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
24
27.01.2020, 19:33
  • 1
    , несоответствие между VAR=... и local VAR=... действительно сбиваемый с толку меня! –  Jonny 10.04.2018, 17:10

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

function testcase()
{
    local MYRESULT

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

    return 0
}

-вывести:

$ testcase
False returned false!
$ 
13
27.01.2020, 19:33

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

set -e
x=$(set -e; false; true)
echo "this will never be shown"
5
27.01.2020, 19:33

Теги

Похожие вопросы