L'esecuzione di exitin 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 1e continua con lo script. Anche sostituire la chiamata con echo $(CALC) || exit 1non aiuta perché il codice di ritorno di echoè 0 indipendentemente dal codice di ritorno di calc. E calcviene eseguito prima di echo.
Ancora più sconcertante è contrastare l'effetto exitavvolgendolo in localbuiltin 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.logper oggi. La data viene inserita da un utente che potrebbe non fornire un valore ragionevole. Pertanto, nella mia funzione, fnamecontrollo il valore restituito di dateper 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.logviene 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 1fine della local …riga non viene mai attivata perché la localdirettiva ritorna sempre 0. Questo perché localviene eseguito dopo $(fname) e quindi sovrascrive il suo codice di ritorno. E per questo motivo, lo script continua e invoca touchcon 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 localall'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 localnon 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.