Перехват ошибок при подстановке команд с использованием «-o errtrace» (т.е. установить -E)

14

Согласно этому справочному руководству :

-E (также -o errtrace)

Если установлено, любая ловушка в ERR наследуется функциями оболочки, подстановками команд и командами, выполняемыми в среде подоболочки. Ловушка ERR обычно не наследуется в таких случаях.

Тем не менее, я должен интерпретировать это неправильно, потому что следующее не работает:

#!/usr/bin/env bash
# -*- bash -*-

set -e -o pipefail -o errtrace -o functrace

function boom {
  echo "err status: $?"
  exit $?
}
trap boom ERR


echo $( made up name )
echo "  ! should not be reached ! "

Я уже знаю простое назначение, my_var=$(made_up_name)выйдет из скрипта с set -e(т.е. errexit).

Является ли -E/-o errtraceдолжен работать как приведенный выше код? Или, скорее всего, я неправильно понял?

dgo.a
источник
2
Это хороший вопрос. Замена echo $( made up name )на $( made up name )производит желаемое поведение. У меня нет объяснения, хотя.
iruvar
Я не знаю насчет bE -E, но знаю, что -e влияет на завершение оболочки только в том случае, если ошибка возникла из-за последней команды в конвейере. Таким образом, ваш var=$( pipe )и $( pipe )примеры будут представлять конечные точки канала, а pipe > echoне будут. Моя страница руководства гласит: «1. Сбой какой-либо отдельной команды в многокомпонентном конвейере не должен приводить к выходу оболочки. Должен рассматриваться только сбой самого конвейера».
mikeserv
Вы можете сделать его неудачным, хотя: echo $ ($ {madeupname?}). Но это установлено -е. Опять же, -Э вне моего собственного опыта.
mikeserv
@mikeserv @ 1_CR Руководство по bash @ echo указывает, что echoвсегда возвращает 0. Это должно учитываться при анализе ...

Ответы:

5

Примечание: zshбудет жаловаться на «плохие шаблоны», если вы не настроите его для принятия «встроенных комментариев» для большинства примеров здесь и не будете запускать их через прокси-оболочку, как я это сделал sh <<-\CMD.

Итак, как я уже говорил в комментариях выше, я не знаю конкретно о bashset -E , но я знаю, что POSIX-совместимые оболочки предоставляют простой способ проверки значения, если вы этого хотите:

    sh -evx <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
        echo "echo still works" 
    }
    _test && echo "_test doesnt fail"
    # END
    CMD
sh: line 1: empty: error string
+ echo

+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail

Вы увидите , что , хотя я использовал , parameter expansionчтобы проверить ${empty?} _test()еще returnS проход - как в проявили последнее echoЭто происходит потому , что не удалось значение убивает $( command substitution )подоболочку , который содержит его, но его родительские оболочки - _testв это время - продолжает грузоперевозку. И echoне важно - это много счастлив служить только \newline; echoэто не тест.

Но учтите это:

    sh -evx <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    _test ||\
            echo "this doesnt even print"
    # END
    CMD
_test+ sh: line 1: empty: function doesnt run

Поскольку я теперь вводил _test()'sвход с предварительно оцененным параметром, функция INIT here-documentтеперь _test()даже не пытается запускаться. Более того, shоболочка, по-видимому, полностью отдает призрак и echo "this doesnt even print" даже не печатает.

Вероятно, это не то, что вы хотите.

Это происходит потому, что ${var?}расширение параметра style предназначено для выходаshell в случае отсутствия параметра, оно работает следующим образом :

${parameter:?[word]}

Указать ошибку, если Nullили Unset.если параметр не установлен или имеет значение NULL, то expansion of word(или сообщение, указывающее, что он не установлен, если слово не written to standard errorуказано ) должно быть и shell exits with a non-zero exit status. В противном случае значение parameter shall be substituted. Интерактивная оболочка не должна выходить.

Я не буду копировать / вставлять весь документ, но если вы хотите потерять set but nullзначение, используйте форму:

${var :? error message }

С, :colonкак указано выше. Если вы хотите, чтобы nullзначение было успешным, просто опустите двоеточие. Вы также можете отрицать это и потерпеть неудачу только для заданных значений, как я покажу позже.

Еще один прогон _test():

    sh <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    echo "this runs" |\
        ( _test ; echo "this doesnt" ) ||\
            echo "now it prints"
    # END
    CMD
this runs
sh: line 1: empty: function doesnt run
now it prints

Это работает со всеми видами быстрых тестов, но выше вы увидите, что _test()запуск из середины pipelineсбоев, и фактически его содержащая command listподоболочка сбоит полностью, так как ни одна из команд в функции не выполняется, ни следующий echoзапуск вообще, хотя также показано, что это может быть легко проверено, потому что echo "now it prints" теперь печатает.

Полагаю, дьявол кроется в деталях. В приведенном выше случае оболочка, которая выходит, не является сценарием, _main | logic | pipelineа ( subshell in which we ${test?} ) ||требует небольшой песочницы.

И это может быть неочевидно, но если вы хотите передать только противоположный случай или только set=значения, это тоже довольно просто:

    sh <<-\CMD
    N= #N is NULL
    _test=$N #_test is also NULL and
    v="something you would rather do without"    
    ( #this subshell dies
        echo "v is ${v+set}: and its value is ${v:+not NULL}"
        echo "So this ${_test:-"\$_test:="} will equal ${_test:="$v"}"
        ${_test:+${N:?so you test for it with a little nesting}}
        echo "sure wish we could do some other things"
    )
    ( #this subshell does some other things 
        unset v #to ensure it is definitely unset
        echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
        echo "So this ${_test:-"\$_test:="} will equal NULL ${_test:="$v"}"
        ${_test:+${N:?is never substituted}}
        echo "so now we can do some other things" 
    )
    #and even though we set _test and unset v in the subshell
    echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
    # END
    CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without

Приведенный выше пример использует все 4 формы POSIX параметра замещения и их различный :colon nullили not nullиспытание. Больше информации в ссылке выше, и вот она снова .

И я думаю, что мы должны показать нашу _testработу функции, верно? Мы просто объявляем empty=somethingв качестве параметра нашей функции (или в любое время заранее):

    sh <<-\CMD
    _test() { echo $( echo ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?tested as a pass before function runs}
    INIT
    echo "this runs" >&2 |\
        ( empty=not_empty _test ; echo "yay! I print now!" ) ||\
            echo "suspiciously quiet"
    # END
    CMD
this runs
not_empty
echo still works
yay! I print now!

Следует отметить, что эта оценка стоит отдельно - она ​​не требует дополнительного теста, чтобы провалиться. Еще пара примеров:

    sh <<-\CMD
    empty= 
    ${empty?null, no colon, no failure}
    unset empty
    echo "${empty?this is stderr} this is not"
    # END
    CMD
sh: line 3: empty: this is stderr

    sh <<-\CMD
    _input_fn() { set -- "$@" #redundant
            echo ${*?WHERES MY DATA?}
            #echo is not necessary though
            shift #sure hope we have more than $1 parameter
            : ${*?WHERES MY DATA?} #: do nothing, gracefully
    }
    _input_fn heres some stuff
    _input_fn one #here
    # shell dies - third try doesnt run
    _input_fn you there?
    # END
    CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?

И вот, наконец, мы возвращаемся к первоначальному вопросу: как обрабатывать ошибки в $(command substitution)подоболочке? Правда в том, что есть два пути, но ни один не прямой. Суть проблемы - процесс оценки оболочки - расширения оболочки (включая$(command substitution) ) происходят раньше в процессе оценки оболочки, чем текущее выполнение команды оболочки - когда ваши ошибки могут быть перехвачены и захвачены.

Проблема заключается в том, что к тому времени, когда текущая оболочка оценивает ошибки, $(command substitution)подоболочка уже была заменена - ошибок не осталось.

Так каковы два пути? Либо вы делаете это явно в рамках$(command substitution) подоболочке с помощью тестов, как без тестов, либо поглощаете ее результаты в текущей переменной оболочки и проверяете ее значение.

Способ 1:

    echo "$(madeup && echo \: || echo '${fail:?die}')" |\
          . /dev/stdin

sh: command not found: madeup
/dev/stdin:1: fail: die

    echo $?

126

Способ 2:

    var="$(madeup)" ; echo "${var:?die} still not stderr"

sh: command not found: madeup
sh: var: die

    echo $?

1

Это не удастся, независимо от количества переменных, объявленных в строке:

   v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"

sh: command not found: madeup
sh: v1: parameter not set

И наше возвращаемое значение остается постоянным:

    echo $?
1

ТЕПЕРЬ ЛОВУШКА:

    trap 'printf %s\\n trap resurrects shell!' ERR
    v1="$(madeup)" v2="$(printf %s\\n shown after trap)"
    echo "${v1:?#1 - still stderr}" "${v2:?invisible}"

sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap

    echo $?
0
Микесерв
источник
Например, практически его точная спецификация: echo $ {v = $ (madeupname)}; echo "$ {v:? trap1st}! не достигнуто!"
mikeserv
1
Подводя итог: Эти расширения параметров являются отличным способом контролировать, установлены ли переменные и т. Д. Что касается состояния выхода echo, я заметил, что echo "abc" "${v1:?}"оно не выполняется (abc никогда не печатался). И оболочка возвращает 1. Это верно даже при наличии команды или без "${v1:?}"нее ( непосредственно на клиенте). Но для сценария OP все, что требовалось для запуска ловушки, - это размещение присваивания его переменной, содержащей замену несуществующей команды в одной строке. В противном случае поведение echo должно возвращать 0 всегда, если оно не прерывается, как в описанных вами тестах.
Просто v=$( madeup ). Я не вижу, что с этим небезопасно. Это просто задание, например, человек неправильно ввел команду v="$(lss)". Это ошибки. Да, вы можете проверить состояние ошибки последней команды $? - потому что это команда в строке (присвоение без имени команды) и ничего больше - не аргумент для echo. Кроме того, здесь она фиксируется функцией как! = 0, поэтому вы получаете обратную связь дважды. Иначе, конечно, как вы объясняете, есть лучший способ сделать это упорядоченно в рамках, но у OP была одна строка: эхо плюс его неудачная замена. Он задавался вопросом об эхо.
Да, в вопросе упоминается простое назначение, а также как само собой разумеющееся, и хотя я не мог дать конкретного совета о том, как использовать -E, я пытался показать, что результаты, аналогичные продемонстрированной проблеме, были более чем возможны, не прибегая к bashisms. В любом случае, именно проблемы, о которых вы упомянули - например, одно назначение на строку - делают такие решения трудными для обработки в конвейере, что я также продемонстрировал, как их обрабатывать. Хотя это правда, что вы говорите - нет ничего опасного в том, чтобы просто назначить его.
mikeserv
1
Хороший вопрос - возможно, потребуется больше усилий. Я думаю об этом.
mikeserv
1

Если установлено, любая ловушка ERR наследуется функциями оболочки, подстановками команд и командами, выполняемыми в среде подоболочек

В вашем скрипте это выполнение команды ( echo $( made up name )). В bash команды разделяются либо ; или с новой строкой . В команде

echo $( made up name )

$( made up name )считается частью команды. Даже если эта часть завершится неудачно и вернется с ошибкой, вся команда будет выполнена успешно, так как echoне знаю об этом. По мере возврата команды с 0 ловушка не срабатывает.

Вы должны поместить это в две команды, назначение и эхо

var=$(made_up_name)
echo $var
Тотти
источник
echo $varне терпит неудачу - $varрасширяется, чтобы опустеть, прежде чем когда-либо echoфактически смотрит на это.
mikeserv
0

Это связано с ошибкой в ​​bash. На этапе подстановки команды в

echo $( made up name )

madeвыполняется (или не может быть найден) в подоболочке, но подоболочка «оптимизирована» таким образом, что она не использует некоторые ловушки из родительской оболочки. Это было исправлено в версии 4.4.5 :

При определенных обстоятельствах простая команда оптимизируется для исключения форка, в результате чего ловушка EXIT не выполняется.

С bash 4.4.5 или выше вы должны увидеть следующий вывод:

error.sh: line 13: made: command not found
err status: 127
  ! should not be reached !

Обработчик ловушек был вызван, как и ожидалось, затем подоболочка завершается. ( set -eтолько вызывает выход подоболочки, а не родительский элемент, так что сообщение «не должно быть достигнуто» должно быть фактически достигнуто.)

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

echo $( ( made up name ) )

Лишние пробелы необходимы для отличия от Арифметического Расширения.

JigglyNaga
источник