Захоплення помилок підстановки команд за допомогою "-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повинен працювати, як описано вище, код? Або, швидше за все, я його неправильно прочитав?


2
Це гарне запитання. Заміна echo $( made up name )на $( made up name )виробляє бажану поведінку. Однак у мене немає пояснень.
iruvar

Я не знаю про bash's -E, але я знаю, що -e впливає на вихід оболонки, лише якщо помилка є результатом останньої команди в конвеєрі. Тож ваш var=$( pipe )і $( pipe )приклади представляють кінцеві точки труби, тоді як pipe > echoні. На моїй сторінці "man" йдеться: "1. Невдача будь-якої окремої команди в багатокомандному трубопроводі не повинна викликати вихід оболонки. Розглядається лише вихід з ладу самого трубопроводу",
mikeserv

Ви можете зробити це не вдалося: echo $ ($ {madeupname?}). Але це встановлено -е. Знову ж таки, -E - це поза моїм власним досвідом.
mikeserv

@mikeserv @ 1_CR Посібник з bash @ echo вказує, що echoзавжди повертається 0. Це потрібно враховувати в аналізі ...

Відповіді:


5

Примітка: zshскаржитеся на "погані шаблони", якщо ви не налаштовуєте його на прийняття "вбудованих коментарів" для більшості прикладів тут і не запускайте їх через проксі-сервер, як я це робив sh <<-\CMD.

Так, як я вже говорив у коментарях вище, я не знаю конкретно про bash'sset -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()раніше returnтестував пропуск - як це доводиться в останньому. 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?}розширення параметрів стилю призначене для виходу зshell у випадку відсутності параметра, воно працює так :

${parameter:?[word]}

Вкажіть помилку, якщо Nullабо Unset.Якщо параметр не встановлено чи недійсний, то 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:?}"безпосередньо на кліпі). Але для сценарію ОП все, що потрібно для запуску пастки, - це розміщення його змінної призначення, що містить підміну для неіснуючої команди тільки в одному рядку. Інакше поведінка ехо полягає у тому, щоб повертати 0 завжди, якщо тільки не перериватись, як з тестами, які ви пояснили.

Просто v=$( madeup ). Я не бачу, що в цьому небезпечно. Це просто завдання, людина, наприклад, ввела в оману команду v="$(lss)". Це помилки. Так, ви можете перевірити стан помилки останньої команди $? - тому що це команда в рядку (завдання без назви команди) та нічого іншого - не аргумент наголос. Плюс, тут він захоплений функцією як! = 0, тому ви отримуєте зворотній зв'язок двічі. Інакше, звичайно, як ви пояснюєте, є кращий спосіб зробити це впорядковано в рамках, але в ОП був один єдиний рядок: відлуння плюс його невдала заміна. Йому було цікаво відлуння.

Так, у запитанні згадується просте завдання, а також як належне, і хоча я не міг запропонувати конкретних порад щодо використання -E, я намагався показати подібні результати, як продемонстроване питання, було більш ніж можливим, не вдаючись до башизми. У будь-якому випадку, саме конкретні проблеми, про які ви згадуєте - як-от одне призначення на рядок - ускладнюють обробку таких рішень у трубопроводі, що я також продемонстрував, як впоратися. Хоча правда те, що ви говорите, - нічого небезпечного в тому, щоб просто призначити це.
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

Це пов’язано з помилкою в баші. Під час кроку командної заміни в

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 ) )

Додаткові пробіли потрібно відрізняти від арифметичного розширення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.