Uwaga: zsh
narzeka 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 expansion
testowałem ${empty?} _test()
wciąż return
s to przepustka - jak wykazano w poprzednim. echo
Dzieje się tak, ponieważ nieudana wartość zabija $( command substitution )
podpowłokę, która ją zawiera, ale jej powłoka macierzysta - _test
w tym czasie - kontynuuje transport. I echo
to nie obchodzi - z przyjemnością podaje się tylko, że \newline; echo
to 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-document
teraz, _test()
funkcja nawet nie próbuje się uruchomić. Co więcej, sh
skorupa 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 Null
lub 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 null
wartości, użyj formularza:
${var
:? error message }
Z :colon
powyższym. Jeśli chcesz, aby null
wartość 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 pipeline
awarii, aw rzeczywistości jego zawierająca command list
podpowł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 null
lub not null
testy. Więcej informacji znajduje się w powyższym linku, a tutaj znowu .
I myślę, że powinniśmy też pokazać naszą _test
funkcję, prawda? Po prostu deklarujemy empty=something
jako 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
echo $( made up name )
przez$( made up name )
powoduje pożądane zachowanie. Nie mam jednak wyjaśnienia.