GNU/Linux >> Znalost Linux >  >> Linux

Zachycování chyb při substituci příkazů pomocí „-o Errtrace“ (tj. Set -e)?

Podle tohoto referenčního manuálu:

-E (také -o errtrace)

Je-li nastavena, jakákoli past na ERR je zděděna funkcemi shellu, substitucemi příkazů a příkazy prováděnými v prostředí subshell. Trap
ERR se v takových případech normálně nedědí.

Musím to však interpretovat špatně, protože následující nefunguje:

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

Jednoduché přiřazení již znám, my_var=$(made_up_name) , ukončí skript pomocí set -e (tj. errexit).

Je -E/-o errtrace má fungovat jako výše uvedený kód? Nebo jsem to s největší pravděpodobností špatně přečetl?

Přijatá odpověď:

Poznámka:zsh bude si stěžovat na „špatné vzory“, pokud jej nenakonfigurujete tak, aby přijímal „vložené komentáře“ pro většinu příkladů zde a nespouštěl je přes proxy shell, jako jsem to udělal s sh <<-CMD .

Dobře, takže, jak jsem uvedl v komentářích výše, nevím konkrétně o bashově set -E , ale vím, že shelly kompatibilní s POSIX poskytují jednoduchý způsob testování hodnoty, pokud si to přejete:

    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

Výše uvidíte, že i když jsem použil parameter expansion k otestování ${empty?} _test() stále return s pass – jak je doloženo v posledním echo K tomu dochází, protože chybná hodnota ukončí $( command substitution ) subshell, který jej obsahuje, ale jeho nadřazený shell – _test v této době – pokračuje v kamionové dopravě. A echo je to jedno – je dost šťastné, že obslouží pouze newline; echo není test.

Ale zvažte toto:

    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

Protože jsem nakrmil _test()'s vstup s předem vyhodnoceným parametrem v INIT here-document nyní _test()'s funkce se ani nepokusí vůbec spustit. A co víc, sh shell se zjevně zcela vzdává ducha a echo "this doesnt even print" ani netiskne.

Pravděpodobně to ne co chcete.

K tomu dochází, protože ${var?} style parameter-expansion je navržen k ukončení shell v případě chybějícího parametru to funguje takto:

${parameter:?[word]}

Pokud Null, označte Error nebo Unset. Pokud je parametr nenastavený nebo null, expansion of word (nebo zpráva oznamující, že není nastavena, pokud je slovo vynecháno) se written to standard error a shell exits with a non-zero exit status . V opačném případě bude nahrazena hodnota parameter shall be substituted . Interaktivní shell se nemusí opouštět.

Nebudu kopírovat/vkládat celý dokument, ale pokud chcete, aby došlo k selhání set but null hodnotu, kterou použijete ve formuláři:

${var 😕 error message }

Pomocí :colon jak je uvedeno výše. Pokud chcete null hodnotu pro úspěch, stačí vynechat dvojtečku. Můžete to také negovat a selhat pouze pro nastavené hodnoty, jak za chvíli ukážu.

Další spuštění _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

Funguje to se všemi druhy rychlých testů, ale výše uvidíte, že _test()'s , spustit ze středu pipeline selže a ve skutečnosti obsahuje command list subshell zcela selže, protože se nespustí žádný z příkazů ve funkci ani následující echo spustit vůbec, i když je také ukázáno, že to lze snadno otestovat, protože echo "now it prints" teď se vytiskne.

Ďábel je v detailech, myslím. Ve výše uvedeném případě shell, který opustí, není _main | logic | pipeline ale ( subshell in which we ${test?} ) || takže je potřeba trochu sandboxingu.

Související:Aplikaci nelze otevřít jako root. Nebyl nalezen příkaz sudo?

A nemusí to být zřejmé, ale pokud jste chtěli použít pouze opačný případ, nebo pouze set= hodnot, je to také docela jednoduché:

    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

Výše uvedený příklad využívá všechny 4 formy substituce parametrů POSIX a jejich různé :colon null nebo not null testy. V odkazu výše je více informací a zde je znovu.

A myslím, že bychom měli ukázat náš _test funkční taky, ne? Pouze deklarujeme empty=something jako parametr naší funkce (nebo kdykoli předtím):

    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!

Je třeba poznamenat, že toto hodnocení je samostatné – pro neúspěšné provedení nevyžaduje žádný další test. Několik dalších příkladů:

    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 -- "[email protected]" #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?

A tak se konečně vracíme k původní otázce:jak zacházet s chybami v $(command substitution) subshell? Pravda je, že existují dva způsoby, ale žádný není přímý. Jádrem problému je proces vyhodnocování shellu – expanze shellu (včetně $(command substitution) ) dochází dříve v procesu vyhodnocování shellu než při provádění aktuálního příkazu shellu – v takovém případě mohou být vaše chyby zachyceny a zachyceny.

Problém, který operační systém zažívá, je ten, že v době, kdy aktuální shell vyhodnotí chyby, $(command substitution) subshell již byl nahrazen – nezůstaly žádné chyby.

Jaké jsou tedy dva způsoby? Buď to uděláte explicitně v rámci $(command substitution) subshell s testy jako bez něj, nebo jeho výsledky absorbujete do aktuální proměnné shellu a otestujete jeho hodnotu.

Související:Příkaz PostgreSQL Select vytváří chyby v datagripu, ale ne v pgAdmin4?

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

Toto
selže bez ohledu na počet proměnných deklarovaných na řádku:

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

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

A naše návratová hodnota zůstává konstantní:

    echo $?
1

TEĎ PAST:

    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

Linux
  1. TCP Proxying pomocí socat

  2. Linuxový příkaz echo

  3. Jak nastavit automatizaci úloh na Linux VPS pomocí Cronu?

  1. Náhrada příkazů:rozdělení na nový řádek, ale ne mezera?

  2. Náhrada příkazů pomocí „?

  3. Příklady příkazů echo v Linuxu

  1. Používáte export v .bashrc?

  2. Změnit písmo v příkazu Echo?

  3. 10 Praktické příklady použití příkazu scp