L'esecuzione di exit
in una subshell è una trappola:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
Lo script stampa 42, esce dalla subshell con codice di ritorno 1
e continua con lo script. Anche sostituire la chiamata con echo $(CALC) || exit 1
non aiuta perché il codice di ritorno di echo
è 0 indipendentemente dal codice di ritorno di calc
. E calc
viene eseguito prima di echo
.
Ancora più sconcertante è contrastare l'effetto exit
avvolgendolo in local
builtin come nel seguente script. Mi sono imbattuto nel problema quando ho scritto una funzione per verificare un valore di input. Esempio:
Voglio creare un file chiamato "year month day.log", cioè 20141211.log
per oggi. La data viene inserita da un utente che potrebbe non fornire un valore ragionevole. Pertanto, nella mia funzione, fname
controllo il valore restituito di date
per verificare la validità dell'input dell'utente:
#!/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"
Sembra buono. Lascia che lo script sia chiamato s.sh
. Se l'utente chiama lo script con ./s.sh "Thu Dec 11 20:45:49 CET 2014"
, il file 20141211.log
viene creato. Se, tuttavia, l'utente digita ./s.sh "Thu hec 11 20:45:49 CET 2014"
, quindi lo script genera:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
La riga fname…
dice che i dati di input errati sono stati rilevati nella sottostruttura. Ma la exit 1
fine della local …
riga non viene mai attivata perché la local
direttiva ritorna sempre 0
. Questo perché local
viene eseguito dopo $(fname)
e quindi sovrascrive il suo codice di ritorno. E per questo motivo, lo script continua e invoca touch
con un parametro vuoto. Questo esempio è semplice ma il comportamento di bash può essere abbastanza confuso in una vera applicazione. Lo so, i veri programmatori non usano la gente del posto
Per chiarire: senza il local
, lo script si interrompe come previsto quando viene inserita una data non valida.
La correzione è di dividere la linea come
local FNAME
FNAME=$(fname "$1") || exit 1
Lo strano comportamento è conforme alla documentazione local
all'interno della pagina man di bash: "Lo stato di ritorno è 0 a meno che non sia usato local al di fuori di una funzione, viene fornito un nome non valido o il nome è una variabile di sola lettura."
Sebbene non sia un bug, ritengo che il comportamento di bash sia controintuitivo. Sono consapevole della sequenza di esecuzione, tuttavia local
non dovrei mascherare un incarico rotto.
La mia risposta iniziale conteneva alcune imprecisioni. Dopo una discussione rivelatrice e approfondita con Mikeserv (grazie per quello) sono andato per risolverli.