Wykonanie exit
w podpowłoce to jedna pułapka:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
Skrypt wypisuje 42, wychodzi z podpowłoki z kodem powrotu 1
i kontynuuje wykonywanie skryptu. Nawet zastąpienie wywołania echo $(CALC) || exit 1
nie pomaga, ponieważ kod powrotu echo
wynosi 0 niezależnie od kodu powrotu calc
. I calc
jest wykonywany przed echo
.
Jeszcze więcej zagadek niweczy efekt exit
zawijania go do local
wbudowanego, jak w poniższym skrypcie. Natknąłem się na problem, gdy napisałem funkcję do weryfikacji wartości wejściowej. Przykład:
Chcę utworzyć plik o nazwie „rok miesiąc dzień.log”, tj. 20141211.log
Na dziś. Data jest wprowadzana przez użytkownika, który może nie podać rozsądnej wartości. Dlatego w mojej funkcji fname
sprawdzam wartość zwracaną w date
celu sprawdzenia poprawności danych wejściowych użytkownika:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
Wygląda dobrze. Niech skrypt ma nazwę s.sh
. Jeśli użytkownik wywoła skrypt za pomocą ./s.sh "Thu Dec 11 20:45:49 CET 2014"
, plik 20141211.log
zostanie utworzony. Jeśli jednak użytkownik wpisze ./s.sh "Thu hec 11 20:45:49 CET 2014"
, skrypt wyświetli:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
Linia fname…
mówi, że w podpowłoce wykryto złe dane wejściowe. Ale exit 1
koniec local …
linii nigdy się nie uruchamia, ponieważ local
dyrektywa zawsze powraca 0
. Wynika to z faktu, że local
jest wykonywany po, $(fname)
a tym samym zastępuje swój kod powrotu. Z tego powodu skrypt jest kontynuowany i wywołuje się touch
z pustym parametrem. Ten przykład jest prosty, ale zachowanie bash może być mylące w prawdziwej aplikacji. Wiem, prawdziwi programiści nie używają miejscowych
Aby było to jasne: bez local
skryptu zostanie przerwany zgodnie z oczekiwaniami po wprowadzeniu niepoprawnej daty.
Rozwiązaniem jest podzielenie linii jak
local FNAME
FNAME=$(fname "$1") || exit 1
Dziwne zachowanie jest zgodne z dokumentacją strony local
podręcznika bash: „Zwracany status to 0, chyba że lokalny jest używany poza funkcją, podana jest niepoprawna nazwa lub nazwa jest zmienną tylko do odczytu”.
Chociaż nie jestem błędem, uważam, że zachowanie bash jest sprzeczne z intuicją. Zdaję sobie sprawę z sekwencji wykonania, local
nie powinien jednak maskować zepsutego zadania.
Moja wstępna odpowiedź zawierała pewne niedokładności. Po odkrywczej i dogłębnej dyskusji z mikeserv (dziękuję za to) postanowiłem je naprawić.