In uno script Bash, come posso uscire da tutto lo script se si verifica una certa condizione?


717

Sto scrivendo una sceneggiatura in Bash per testare del codice. Tuttavia, sembra sciocco eseguire i test se la compilazione del codice non riesce in primo luogo, nel qual caso interromperò solo i test.

C'è un modo in cui posso farlo senza racchiudere l'intero script all'interno di un ciclo while e usare pause? Qualcosa come un dun dun dun goto?

Risposte:


810

Prova questa affermazione:

exit 1

Sostituire 1con codici di errore appropriati. Vedi anche Codici di uscita con significati speciali .


4
@CMCDragonkai, di solito funziona qualsiasi codice diverso da zero. Se non hai bisogno di qualcosa di speciale, puoi semplicemente usarlo in modo 1coerente. Se lo script deve essere eseguito da un altro script, è possibile che si desideri definire il proprio set di codice di stato con un significato particolare. Ad esempio, 1== test falliti, 2== compilazione fallita. Se lo script fa parte di qualcos'altro, potrebbe essere necessario modificare i codici in modo che corrispondano alle pratiche ivi utilizzate. Ad esempio, quando parte della suite di test eseguita da automake, il codice 77viene utilizzato per contrassegnare un test ignorato.
Michał Górny,

21
no, questo chiude anche la finestra, non solo esce dalla sceneggiatura
Toni Leigh,

7
@ToniLeigh bash non ha il concetto di "finestra", probabilmente sei confuso riguardo a ciò che fa il tuo particolare setup - ad esempio un emulatore di terminale.
Michael Foukarakis,

4
@ToniLeigh Se sta chiudendo la "finestra" probabilmente stai inserendo il exit #comando all'interno di una funzione, non in uno script. (Nel qual caso utilizzare return #invece.)
Jamie

1
@Sevenearths 0 significa che ha avuto successo, quindi exit 0esce dallo script e restituisce 0 (dicendo ad altri script che potrebbero usare il risultato di questo script è riuscito)
VictorGalisson

689

Usa set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Lo script verrà terminato dopo la prima riga non riuscita (restituisce un codice di uscita diverso da zero). In questo caso, command-that-fail2 non verrà eseguito.

Se dovessi controllare lo stato di ritorno di ogni singolo comando, il tuo script sarebbe simile al seguente:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Con set -e sembrerebbe:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Qualsiasi comando che fallisce farà fallire l'intero script e restituirà uno stato di uscita che puoi controllare con $? . Se il tuo script è molto lungo o stai costruendo un sacco di cose, diventerà piuttosto brutto se aggiungi controlli di stato del reso ovunque.


10
Con set -eHai ancora in grado di fare un po 'di uscita comandi con errori senza fermare lo script: command 2>&1 || echo $?.
Adobe,

6
set -einterromperà lo script se una pipeline o una struttura di comandi restituiscono un valore diverso da zero. Ad esempio foo || barfallirà solo se entrambi fooe barrestituirà un valore diverso da zero. Di solito uno script bash ben scritto funzionerà se lo aggiungi set -eall'inizio e l'aggiunta funziona come un controllo di integrità automatizzato: interrompi lo script se qualcosa va storto.
Mikko Rantalainen,

6
Se esegui il piping dei comandi insieme, puoi anche fallire se qualcuno di loro fallisce impostando l' set -o pipefailopzione.
Jake Biesinger,

18
In realtà il codice idiomatico senza set -esarebbe giusto make || exit $?.
Tripleee,

4
Anche tu hai set -u. Date un'occhiata alla non ufficiale strict mode bash : set -euo pipefail.
Pablo A

236

Un ragazzo di SysOps una volta mi ha insegnato la tecnica dell'artiglio a tre dita:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Queste funzioni sono * NIX OS e shell robusto. Inseriscili all'inizio del tuo script (bash o altro), try()dichiarazione e codice.

Spiegazione

(basato sul commento di una pecora volante ).

  • yell: stampa il nome dello script e tutti gli argomenti su stderr:
    • $0 è il percorso della sceneggiatura;
    • $* sono tutti argomenti.
    • >&2significa >reindirizzare stdout a & pipe2 . la pipa1 sarebbe se stdoutstessa.
  • diefa lo stesso di yell, ma esce con uno stato di uscita diverso da 0 , che significa "errore".
  • tryusa il ||(booleano OR), che valuta il lato destro solo se quello sinistro fallisce.
    • $@sono di nuovo tutti gli argomenti, ma diversi .

3
Sono abbastanza nuovo per unix scripting. Puoi spiegare come vengono eseguite le funzioni sopra? Sto vedendo una nuova sintassi che non conosco. Grazie.
kaizenCoder

15
urlo : $0è il percorso della sceneggiatura. $*sono tutti argomenti. >&2significa " >reindirizzare stdout alla &pipe 2". pipe 1 sarebbe stdout stesso. quindi urlare antepone tutti gli argomenti con il nome dello script e stampa su stderr. die fa lo stesso di urlo , ma esce con uno stato di uscita diverso da 0, che significa "fallimento". try usa il booleano o ||, che valuta il lato destro solo se quello sinistro non ha fallito. $@sono di nuovo tutti gli argomenti, ma diversi . spero che spieghi tutto
volare pecore il

1
Ho modificato questo in die() { yell "$1"; exit $2; }modo da poter passare un messaggio e un codice di uscita con die "divide by zero" 115.
Mark Lakata,

3
Posso vedere come userei yelle die. Tuttavia, trynon così tanto. Puoi fornire un esempio di come lo usi?
kshenoy,

2
Hmm, ma come li usi in una sceneggiatura? Non capisco cosa intendi con "try () la tua dichiarazione e il tuo codice".
TheJavaGuy-Ivan Milosavljević il

33

Se si invocherà lo script con source, è possibile utilizzare return <x>dove <x>sarà lo stato di uscita dello script (utilizzare un valore diverso da zero per errore o falso). Ma se invochi uno script eseguibile (cioè, direttamente con il suo nome file), l'istruzione return restituirà un reclamo (messaggio di errore "return: può solo" restituire "da una funzione o uno script di provenienza").

Se exit <x>invece viene utilizzato, quando viene invocato lo script source, si verificherà l'uscita dalla shell che ha avviato lo script, ma uno script eseguibile termina semplicemente, come previsto.

Per gestire entrambi i casi nello stesso script, è possibile utilizzare

return <x> 2> /dev/null || exit <x>

Questo gestirà qualunque invocazione possa essere adatta. Ciò presuppone che userete questa affermazione al livello più alto dello script. Vorrei sconsigliare di uscire direttamente dallo script all'interno di una funzione.

Nota: <x>dovrebbe essere solo un numero.


Non funziona per me in uno script con un ritorno / uscita all'interno di una funzione, cioè è possibile uscire all'interno della funzione senza esistere la shell, ma senza fare in modo che il chiamante della funzione si occupi del corretto controllo del codice di ritorno della funzione ?
gennaio

@jan Quello che il chiamante fa (o non fa) con i valori di ritorno, è completamente ortogonale (cioè indipendente da) come si ritorna dalla funzione (... senza uscire dalla shell, indipendentemente dall'invocazione). Dipende principalmente dal codice del chiamante, che non fa parte di queste domande e risposte. Potresti persino adattare il valore di ritorno della funzione alle esigenze del chiamante, ma questa risposta non limita ciò che questo valore di ritorno può essere ...
kavadias

11

Includo spesso una funzione chiamata run () per gestire gli errori. Ogni chiamata che desidero effettuare viene passata a questa funzione, quindi l'intero script viene chiuso quando si verifica un errore. Il vantaggio di questo rispetto alla soluzione set -e è che lo script non si chiude silenziosamente quando una linea fallisce e può dirti qual è il problema. Nell'esempio seguente, la terza riga non viene eseguita perché lo script esce alla chiamata su false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"

1
Amico, per qualche ragione, mi piace molto questa risposta. Riconosco che è un po 'più complicato, ma sembra così utile. E dato che non sono un esperto bash, mi porta a credere che la mia logica sia difettosa e che ci sia qualcosa di sbagliato in questa metodologia, altrimenti, penso che gli altri avrebbero dato più lode. Quindi, qual è il problema con questa funzione? C'è qualcosa che dovrei cercare qui?

Non ricordo il motivo per cui ho usato eval, la funzione funziona bene con cmd_output = $ ($ 1)
Joseph Sheedy,

L'ho appena implementato come parte di un processo di distribuzione complesso e ha funzionato alla grande. Grazie ed ecco un commento e un voto.
fuzzygroup,

Lavoro davvero straordinario! Questa è la soluzione più semplice e pulita che funziona bene. Per me l'ho aggiunto prima di un comando in un ciclo FOR poiché i cicli FOR non raccolgono il set -e option. Quindi il comando, dal momento che è con argomenti, ho usato le virgolette singole per evitare problemi di bash in questo modo: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Nota Ho cambiato il nome della funzione per eseguire Try.
Tony-Caffe,

1
evalè potenzialmente pericoloso se si accetta un input arbitrario, ma per il resto sembra piuttosto carino.
dragon788,

5

Invece di ifcostruire, puoi sfruttare la valutazione del corto circuito :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Nota la coppia di parentesi che è necessaria a causa della priorità dell'operatore di alternanza. $?è una variabile speciale impostata per chiudere il codice dell'ultimo comando chiamato.


1
se lo faccio command -that --fails || exit $?funziona senza parentesi, di cosa echo $[4/0]ci fa avere bisogno?
Anentropico,

2
@Anentropic @skalee Le parentesi non hanno nulla a che fare con la precedenza, ma la gestione delle eccezioni. Una divisione per zero causerà un'uscita immediata dalla shell con il codice 1. Senza le parentesi (cioè una semplice echo $[4/0] || exit $?) bash non eseguirà mai il echo, figuriamoci obbedire al ||.
bobbogo,

1

Ho la stessa domanda ma non posso farla perché sarebbe un duplicato.

La risposta accettata, usando exit, non funziona quando lo script è un po 'più complicato. Se si utilizza un processo in background per verificare la condizione, exit esce solo da quel processo, poiché viene eseguito in una sotto-shell. Per uccidere lo script, devi ucciderlo esplicitamente (almeno questo è l'unico modo che conosco).

Ecco un piccolo script su come farlo:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Questa è una risposta migliore ma ancora incompleta e non so davvero come sbarazzarmi della parte del boom .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.