Błędy zalewkowania w podstawianiu poleceń za pomocą „-o errtrace” (tj. Ustaw -E)

14

Zgodnie z tym podręcznikiem :

-E (także -o errtrace)

Jeśli jest ustawiony, każda pułapka na ERR jest dziedziczona przez funkcje powłoki, podstawienia poleceń i polecenia wykonywane w środowisku podpowłoki. Pułapka ERR zwykle nie jest dziedziczona w takich przypadkach.

Jednak muszę to interpretować nieprawidłowo, ponieważ następujące działania nie działają:

#!/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 ! "

Znam już proste przypisanie, my_var=$(made_up_name)zakończy skrypt za pomocą set -e(tj. Errexit).

Czy -E/-o errtracepowinien działać jak powyższy kod? Czy najprawdopodobniej źle to odczytałem?

dgo.a
źródło
2
To dobre pytanie. Zastąpienie echo $( made up name )przez $( made up name )powoduje pożądane zachowanie. Nie mam jednak wyjaśnienia.
iruvar
Nie wiem o -E bash, ale wiem, że -e wpływa na wyjście powłoki tylko wtedy, gdy błąd wynika z ostatniego polecenia w potoku. Zatem zarówno twój, jak var=$( pipe )i $( pipe )przykłady reprezentowałyby punkty końcowe potoku, podczas gdy pipe > echonie. Moja strona podręcznika mówi: „1. Niepowodzenie jakiegokolwiek pojedynczego polecenia w potoku wielozadaniowym nie spowoduje wyjścia powłoki. Należy wziąć pod uwagę tylko awarię samego potoku.”
mikeserv
Możesz jednak sprawić, że się nie powiedzie: echo $ ($ {madeupname?}). Ale to jest ustawione -e. Ponownie, -E jest poza moim własnym doświadczeniem.
mikeserv
@mikeserv @ 1_CR Instrukcja bash @ echo wskazuje, że echozawsze zwraca 0. To musi być uwzględnione w analizie ...

Odpowiedzi:

5

Uwaga: zshnarzeka na „złe wzorce”, jeśli nie skonfigurujesz go tak, aby akceptował „komentarze śródliniowe” dla większości przykładów tutaj i nie uruchamiasz ich przez powłokę proxy, jak to zrobiłem sh <<-\CMD.

Ok, więc, jak powiedziałem w powyższych komentarzach, nie wiem konkretnie o bash'achset -E , ale wiem, że powłoki zgodne z POSIX zapewniają prosty sposób testowania wartości, jeśli chcesz:

    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

Powyżej zobaczysz, że chociaż parameter expansiontestowałem ${empty?} _test()wciąż returns to przepustka - jak wykazano w poprzednim. echoDzieje się tak, ponieważ nieudana wartość zabija $( command substitution )podpowłokę, która ją zawiera, ale jej powłoka macierzysta - _testw tym czasie - kontynuuje transport. I echoto nie obchodzi - z przyjemnością podaje się tylko, że \newline; echoto nie jest test.

Ale rozważ to:

    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

Ponieważ nakarmiłem _test()'s dane wejściowe z wstępnie oszacowanym parametrem w INIT here-documentteraz, _test()funkcja nawet nie próbuje się uruchomić. Co więcej, shskorupa najwyraźniej całkowicie oddaje ducha i echo "this doesnt even print" nawet nie drukuje.

Prawdopodobnie nie tego chcesz.

Dzieje się tak, ponieważ ${var?}ekspansja parametrów stylu to zaprojektowana tak, aby wychodzićshell w przypadku braku parametru, działa to tak :

${parameter:?[word]}

Wskazać błąd, jeśli Nulllub Unset.Jeśli parametr jest nieustawiony lub zerowy, expansion of word(lub komunikat wskazujący, że nie jest ustawiony, jeśli słowo zostanie pominięte)written to standard error a shell exits with a non-zero exit status. W przeciwnym razie wartość parameter shall be substituted. Interaktywna powłoka nie musi wychodzić.

Nie będę kopiować / wklejać całego dokumentu, ale jeśli chcesz niepowodzenia dla set but nullwartości, użyj formularza:

${var :? error message }

Z :colonpowyższym. Jeśli chcesz, aby nullwartość się powiodła, po prostu pomiń dwukropek. Możesz też to zanegować i zawieść tylko dla ustawionych wartości, jak pokażę za chwilę.

Kolejna seria _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

Działa to z wszelkiego rodzaju szybkimi testami, ale powyżej zobaczysz, że _test()działa od środka pipelineawarii, aw rzeczywistości jego zawierająca command listpodpowłoka zawiedzie całkowicie, ponieważ żadne z poleceń w funkcji nie działa ani następująceecho uruchamia się wcale, , chociaż pokazano również, że można go łatwo przetestować, ponieważ echo "now it prints" teraz drukuje.

Diabeł tkwi w szczegółach. W powyższym przypadku powłoka, która wychodzi, nie należy do skryptu_main | logic | pipeline ale ( subshell in which we ${test?} ) ||wymagane jest trochę piaskownicy.

I może nie być to oczywiste, ale jeśli chcesz przejść tylko w przeciwnym przypadku lub tylko set= wartości, jest to również dość proste:

    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

Powyższy przykład wykorzystuje wszystkie 4 formy podstawiania parametrów POSIX i ich różne :colon nulllub not nulltesty. Więcej informacji znajduje się w powyższym linku, a tutaj znowu .

I myślę, że powinniśmy też pokazać naszą _testfunkcję, prawda? Po prostu deklarujemy empty=somethingjako parametr naszej funkcji (lub wcześniej):

    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!

Należy zauważyć, że ta ocena jest samodzielna - nie wymaga żadnego dodatkowego testu, aby zakończyć się niepowodzeniem. Kilka innych przykładów:

    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?

I w końcu wracamy do pierwotnego pytania: jak radzić sobie z błędami w $(command substitution)podpowłoce?Prawda jest taka - istnieją dwa sposoby, ale żaden nie jest bezpośredni. Istotą problemu jest proces oceny powłoki - rozszerzenia powłoki (w tym $(command substitution)) następują wcześniej w procesie oceny powłoki niż bieżące wykonywanie poleceń powłoki - wtedy można uchwycić i uwięzić błędy.

Problem polega na tym, że zanim bieżąca powłoka ocenia błędy, $(command substitution) podpowłoka została już podstawiona - nie ma już żadnych błędów.

Więc jakie są dwa sposoby? Albo zrobisz to jawnie w$(command substitution) podpowłoce za pomocą testów, tak jak byś bez niego, albo absorbujesz jego wyniki do bieżącej zmiennej powłoki i testujesz jej wartość.

Metoda 1:

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

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

    echo $?

126

Metoda 2:

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

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

    echo $?

1

Nie powiedzie się to bez względu na liczbę zmiennych zadeklarowanych w wierszu:

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

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

Nasza wartość zwrotna pozostaje stała:

    echo $?
1

TERAZ TRAP:

    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
mikeserv
źródło
Na przykład praktycznie jego dokładna specyfikacja: echo $ {v = $ (madeupname)}; echo "$ {v:? trap1st}! nie osiągnięto!"
mikeserv
1
Podsumowując: te rozszerzenia parametrów są świetnym sposobem na kontrolowanie, czy zmienne są ustawione itp. W odniesieniu do statusu wyjścia echa zauważyłem, że echo "abc" "${v1:?}"nie wydaje się on działać (abc nigdy nie jest drukowany). Powłoka zwraca 1. Jest to prawdą nawet z komendą lub bez niej ( "${v1:?}"bezpośrednio na cli). Jednak w przypadku skryptu OP wszystko, co było wymagane do wyzwolenia pułapki, to umieszczenie jego zmiennej zmiennej zawierającej podstawienie nieistniejącej komendy w jednym wierszu. W przeciwnym razie zachowanie echa polega na zwróceniu zawsze 0, chyba że zostanie przerwane, tak jak w testach, które wyjaśniłeś.
Właśnie v=$( madeup ). Nie widzę w tym niebezpiecznego. To tylko zadanie, na przykład osoba źle odczytała polecenie v="$(lss)". To błędy. Tak, możesz zweryfikować za pomocą statusu błędu ostatniego polecenia $? - ponieważ jest to polecenie w wierszu (przypisanie bez nazwy polecenia) i nic więcej - nie argument do echa. Dodatkowo tutaj jest uwięziony przez funkcję as! = 0, więc otrzymujesz informację zwrotną dwukrotnie. W przeciwnym razie z pewnością, jak wyjaśnisz, istnieje lepszy sposób na uporządkowanie tego w ramach, ale OP miał 1 pojedynczą linię: echo plus jego nieudane podstawienie. Zastanawiał się nad echem.
Tak, proste zadanie jest wymienione w pytaniu, a także jako pewnik, i chociaż nie mogłem zaoferować konkretnej porady, jak korzystać z -E, starałem się pokazać, że podobne wyniki do wykazanego problemu były bardziej niż możliwe bez uciekania się do bashisms. W każdym razie to właśnie wspomniane problemy - takie jak pojedyncze przypisanie na linię - sprawiają, że takie rozwiązania są trudne w obsłudze w potoku, co również pokazałem, jak sobie z tym poradzić. Chociaż to prawda, co mówisz - po prostu przypisanie tego nie jest niebezpieczne.
mikeserv
1
Dobra uwaga - być może potrzeba większego wysiłku. Zastanawiam się nad tym.
mikeserv
1

Jeśli jest ustawiony, każda pułapka na ERR jest dziedziczona przez funkcje powłoki, podstawienia poleceń i polecenia wykonywane w środowisku podpowłoki

W twoim skrypcie jest to wykonanie polecenia ( echo $( made up name )). W bash polecenia są rozdzielone albo ; lub z nową linią . W poleceniu

echo $( made up name )

$( made up name )jest uważany za część polecenia. Nawet jeśli ta część zawiedzie i powróci z błędem, całe polecenie zostanie wykonane pomyślnie, ponieważ echonie wiem o tym. Gdy polecenie powróci z 0, nie uruchomiono pułapki.

Musisz umieścić go w dwóch poleceniach, przypisaniu i echu

var=$(made_up_name)
echo $var
Totti
źródło
echo $varnie zawiedzie - $varjest powiększany do pustego, zanim w ogóle echogo obejrzy.
mikeserv
0

Wynika to z błędu w bashu. W trakcie zastępowania polecenia w

echo $( made up name )

madedziała (lub nie można go znaleźć) w podpowłoce, ale podpowłoka jest „zoptymalizowana” w taki sposób, że nie używa pułapek z powłoki nadrzędnej. Zostało to naprawione w wersji 4.4.5 :

W pewnych okolicznościach proste polecenie jest zoptymalizowane w celu wyeliminowania rozwidlenia, co powoduje, że pułapka EXIT nie jest wykonywana.

W przypadku wersji bash 4.4.5 lub nowszej powinieneś zobaczyć następujące dane wyjściowe:

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

Program obsługi pułapek został wywołany zgodnie z oczekiwaniami, a następnie podpowłoka kończy działanie. ( set -epowoduje jedynie zamknięcie podpowłoki, a nie elementu nadrzędnego, więc komunikat „nie powinien zostać osiągnięty” powinien zostać osiągnięty.)

Obejściem starszych wersji jest wymuszenie utworzenia pełnej, niezoptymalizowanej podpowłoki:

echo $( ( made up name ) )

Dodatkowe odstępy są wymagane do odróżnienia od rozszerzenia arytmetycznego.

JigglyNaga
źródło