C'è una dichiarazione "goto" in bash?


206

C'è una dichiarazione "goto" in bash? So che è considerata una cattiva pratica, ma ho bisogno di "goto".


4
No, non c'è gotoin bash (almeno dice command not foundper me). Perché? È probabile che ci sia un modo migliore per farlo.
Niklas B.

153
Potrebbe avere le sue ragioni. Ho trovato questa domanda perché desidero che gotoun'istruzione salti un sacco di codice per il debug di uno script di grandi dimensioni senza attendere un'ora per il completamento di varie attività non correlate. Non userei certamente un gotocodice nel codice di produzione, ma per il debug del mio codice, renderebbe la mia vita infinitamente più semplice e sarebbe più facile individuare quando si trattava di rimuoverlo.
Karl Nicoll,

30
@delnan Ma non avere goto può rendere alcune cose più complicate. Esistono davvero casi d'uso.
glglgl,

72
Sono stufo di questo mito goto! Non c'è niente di sbagliato in goto! Tutto ciò che scrivi alla fine diventa goto. In assemblatore, c'è solo goto. Un buon motivo per usare goto in linguaggi di programmazione più alti è ad esempio saltare fuori dai loop nidificati in modo pulito e leggibile.
Alexander Czutro,

25
"Evita goto" è una grande regola. Come ogni regola, dovrebbe essere appreso in tre fasi. Primo: segui la regola fino alla sua seconda natura. In secondo luogo, impara a capire i motivi della regola. In terzo luogo, apprendi buone eccezioni alla regola, sulla base di una comprensione globale di come seguire la regola e i motivi della regola. Evita di saltare i passaggi precedenti, che sarebbe come usare "goto". ;-)
Jeff Learman,

Risposte:


75

No non c'è; vedi §3.2.4 "comandi composti" in Bash Manuale di riferimento per informazioni sulle strutture di controllo che fanno esistere. In particolare, nota la menzione di breake continue, che non sono così flessibili come goto, ma sono più flessibili in Bash che in alcune lingue e possono aiutarti a ottenere ciò che desideri. (Qualunque cosa tu voglia ...)


10
Potresti espandere "più flessibile in Bash che in alcune lingue"?
user239558,

21
@ user239558: Alcune lingue che consentono solo breako continuedal ciclo più interno, mentre Bash consente di specificare il numero di livelli di ciclo per saltare. (E anche delle lingue che ti consentono di breako continueda cicli arbitrari, la maggior parte richiede che sia espressa staticamente - ad esempio, break foo;uscirà dal ciclo etichettato foo- mentre in Bash è espressa in modo dinamico - ad esempio, break "$foo"uscirà dai $foocicli. )
ruakh,

Consiglierei di definire le funzioni con le funzionalità necessarie e di metterle al di fuori di altre parti del codice.
sabato

@ruakh In realtà, molte lingue lo consentono break LABEL_NAME;, piuttosto che il disgustoso di Bash break INTEGER;.
Sapphire_Brick il

1
@Sapphire_Brick: Sì, l'ho menzionato nel commento a cui stai rispondendo.
Ruakh

125

Se lo stai usando per saltare parte di uno script di grandi dimensioni per il debug (vedi il commento di Karl Nicoll), allora se falso potrebbe essere una buona opzione (non sono sicuro che "falso" sia sempre disponibile, per me è in / bin / false) :

# ... Code I want to run here ...

if false; then

# ... Code I want to skip here ...

fi

# ... I want to resume here ...

La difficoltà si presenta quando è il momento di estrarre il codice di debug. Il costrutto "if false" è piuttosto semplice e memorabile, ma come trovi il fi corrispondente? Se il tuo editor ti consente di bloccare il rientro, puoi rientrare nel blocco saltato (quindi ti consigliamo di rimetterlo al termine). O un commento sulla linea di fi, ma dovrebbe essere qualcosa che ricorderete, che sospetto sarà molto dipendente dal programmatore.


6
falseè sempre disponibile. Ma se hai un blocco di codice che non vuoi eseguire, commentalo. Oppure eliminalo (e cerca nel tuo sistema di controllo del codice sorgente se devi recuperarlo in seguito).
Keith Thompson,

1
Se il blocco di codice è troppo lungo per commentare noiosamente una riga alla volta, vedi questi trucchi. stackoverflow.com/questions/947897/… Tuttavia, questi non aiutano neanche un editor di testo a coincidere dall'inizio alla fine, quindi non rappresentano un grande miglioramento.
Camille Goudeseune,

4
"if false" è spesso molto meglio che commentare il codice, perché garantisce che il codice allegato continui ad essere codice legale. La migliore scusa per commentare il codice è quando deve davvero essere cancellato, ma c'è qualcosa che deve essere ricordato - quindi è solo un commento e non più "codice".
Jeff Learman,

1
Uso il commento '#if false;'. In questo modo posso solo cercarlo e trovare sia l'inizio che la fine della sezione di rimozione del debug.
Jon

Questa risposta non dovrebbe essere annullata poiché non risponde in alcun modo alla domanda del PO.
Viktor Joras,

54

In effetti, può essere utile per alcune esigenze di debug o dimostrative.

Ho scoperto che la soluzione Bob Copeland http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant:

#!/bin/bash
# include this boilerplate
function jumpto
{
    label=$1
    cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$')
    eval "$cmd"
    exit
}

start=${1:-"start"}

jumpto $start

start:
# your script goes here...
x=100
jumpto foo

mid:
x=101
echo "This is not printed!"

foo:
x=${x:-10}
echo x is $x

risulta in:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101

36
"La mia ricerca di far sembrare Bash un linguaggio di assemblaggio sempre più vicino al completamento." - Wow. Solo wow.
Brian Agnew,

5
l'unica cosa che cambierei è far sì che le etichette inizino così in : start:modo che non siano errori di sintassi.
Alexej Magura,

5
Sarebbe meglio se lo facessi: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') con etichette che iniziano come: # inizio: => questo impedirebbe errori di script
access_granted

2
Hm, davvero molto probabilmente il mio errore. Ho un post di modifica. Grazie.
Hubbitus,

2
set -xaiuta a capire cosa sta succedendo
John Lin,

30

Puoi usare casein bash per simulare un goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

produce:

bar
star

4
Si noti che questo richiede bash v4.0+. Tuttavia, non è gotoun'opzione generica ma un'opzione fall-through per l' caseaffermazione.
mklement0,

3
penso che questa dovrebbe essere la risposta. ho un vero bisogno di andare al fine di supportare riprendere l'esecuzione di uno script, da una determinata istruzione. questo è, in ogni modo, ma gli zuccheri semantici, goto, semantici e sintattici sono carini, ma non strettamente necessari. ottima soluzione, IMO.
Nathan G,

1
@nathang, se la risposta dipende dal fatto che il tuo caso coincida con il sottoinsieme del caso generale richiesto dall'OP. Sfortunatamente, la domanda si pone sul caso generale, rendendo questa risposta troppo ristretta per essere corretta. (Se tale questione debba essere chiusa in modo troppo ampio per tale motivo è una discussione diversa).
Charles Duffy,

1
andare è più che selezionare. goto feature significa essere in grado di saltare ai luoghi in base ad alcune condizioni, creando persino un loop come flusso ...
Sergio Abreu,

19

Se stai testando / eseguendo il debug di uno script bash e vuoi semplicemente saltare avanti una o più sezioni di codice, ecco un modo molto semplice per farlo che è anche molto facile da trovare e rimuovere in seguito (a differenza della maggior parte dei metodi descritto sopra).

#!/bin/bash

echo "Run this"

cat >/dev/null <<GOTO_1

echo "Don't run this"

GOTO_1

echo "Also run this"

cat >/dev/null <<GOTO_2

echo "Don't run this either"

GOTO_2

echo "Yet more code I want to run"

Per riportare lo script alla normalità, basta eliminare tutte le righe con GOTO.

Possiamo anche prettificare questa soluzione, aggiungendo un gotocomando come alias:

#!/bin/bash

shopt -s expand_aliases
alias goto="cat >/dev/null <<"

goto GOTO_1

echo "Don't run this"

GOTO_1

echo "Run this"

goto GOTO_2

echo "Don't run this either"

GOTO_2

echo "All done"

Gli alias di solito non funzionano negli script bash, quindi abbiamo bisogno del shoptcomando per risolverlo.

Se vuoi essere in grado di abilitare / disabilitare i tuoi goto, abbiamo bisogno di un po 'di più:

#!/bin/bash

shopt -s expand_aliases
if [ -n "$DEBUG" ] ; then
  alias goto="cat >/dev/null <<"
else
  alias goto=":"
fi

goto '#GOTO_1'

echo "Don't run this"

#GOTO1

echo "Run this"

goto '#GOTO_2'

echo "Don't run this either"

#GOTO_2

echo "All done"

Quindi puoi farlo export DEBUG=TRUEprima di eseguire lo script.

Le etichette sono commenti, quindi non causeranno errori di sintassi se disabilitiamo i nostri goto(impostando gotosu ' :' no-op), ma questo significa che dobbiamo citarli nei nostrigoto dichiarazioni.

Ogni volta che usi qualsiasi tipo di gotosoluzione, devi fare attenzione che il codice che stai saltando non imposta alcuna variabile su cui fai affidamento in seguito - potresti dover spostare quelle definizioni nella parte superiore dello script, o appena sopra una delle tue gotodichiarazioni.


Senza entrare nella bontà / cattiveria dei goto, la soluzione di Laurence è semplicemente fantastica. Hai ottenuto il mio voto.
Mogens TrasherDK,

1
È più come saltare, if(false)sembra avere più senso (per me).
venerdì

2
Citare la goto "etichetta" significa anche che qui-doc non viene espanso / valutato, il che ti salva da alcuni bug difficili da trovare (non quotati, sarebbe soggetto a: espansione dei parametri, sostituzione dei comandi ed espansione aritmetica, che tutti possono avere effetti collaterali). Puoi anche modificarlo un po ' alias goto=":<<"e rinunciarvi del cattutto.
signor spuratic

sì, vesperto, puoi usare un'istruzione 'if', e l'ho fatto in molti script, ma diventa molto disordinato ed è molto più difficile da controllare e tenere traccia, specialmente se vuoi cambiare frequentemente i tuoi punti di salto.
Laurence Renshaw,

12

Sebbene altri abbiano già chiarito che non esiste un gotoequivalente diretto in bash (e fornito le alternative più vicine come funzioni, loop e break), vorrei illustrare come l'uso di un loop plus breakpossa simulare un tipo specifico di istruzione goto.

La situazione in cui trovo questo il più utile è quando devo tornare all'inizio di una sezione di codice se non sono soddisfatte determinate condizioni. Nell'esempio seguente, il ciclo while verrà eseguito per sempre fino a quando il ping non interrompe il rilascio dei pacchetti a un IP di prova.

#!/bin/bash

TestIP="8.8.8.8"

# Loop forever (until break is issued)
while true; do

    # Do a simple test for Internet connectivity
    PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]")

    # Exit the loop if ping is no longer dropping packets
    if [ "$PacketLoss" == 0 ]; then
        echo "Connection restored"
        break
    else
        echo "No connectivity"
    fi
done

7

Questa soluzione presentava i seguenti problemi:

  • Rimuove indiscriminatamente tutte le righe di codice che terminano con a :
  • Tratta label:ovunque su una linea come un'etichetta

Ecco una versione fissa ( shell-checkpulita):


#!/bin/bash

# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
function goto
{
 local label=$1
 cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
 eval "$cmd"
 exit
}

start=${1:-start}
goto "$start"  # GOTO start: by default

#start:#  Comments can occur after labels
echo start
goto end

  # skip: #  Whitespace is allowed
echo this is usually skipped

# end: #
echo end

6

C'è ancora una possibilità per ottenere i risultati desiderati: comando trap. Ad esempio, può essere utilizzato per ripulire gli scopi.


4

Non c'è gotoin bash.

Ecco un po 'di soluzione sporca con trapcui si salta solo all'indietro :)

#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT

echo foo
goto trap 2> /dev/null
echo bar

Produzione:

$ ./test.sh 
foo
I am
here now.

Questo non dovrebbe essere usato in quel modo, ma solo per scopi educativi. Ecco perché funziona:

trapsta utilizzando la gestione delle eccezioni per ottenere la modifica del flusso di codice. In questo caso, trapsta rilevando tutto ciò che provoca la USCITA dello script. Il comando gotonon esiste e quindi genera un errore che normalmente uscirebbe dallo script. Questo errore viene rilevato trape 2>/dev/nullnasconde il messaggio di errore che verrebbe normalmente visualizzato.

Questa implementazione di goto non è ovviamente affidabile, poiché qualsiasi comando inesistente (o qualsiasi altro errore, in quel modo), eseguirà lo stesso comando trap. In particolare, non puoi scegliere quale etichetta scegliere.


Fondamentalmente in uno scenario reale non hai bisogno di alcuna dichiarazione goto, sono ridondanti poiché le chiamate casuali in luoghi diversi rendono solo difficile capire il tuo codice.

Se il tuo codice viene invocato molte volte, allora considera di usare il ciclo e cambiando il suo flusso di lavoro per usare continuee break.

Se il tuo codice si ripete da solo, considera di scrivere la funzione e chiamarla tutte le volte che vuoi.

Se il codice deve passare a una sezione specifica in base al valore della variabile, prendere in considerazione l'utilizzo casedell'istruzione.

Se riesci a separare il tuo codice lungo in pezzi più piccoli, considera di spostarlo in file separati e chiamali dallo script principale.


quali sono le differenze tra questo modulo e una funzione normale?
yurenchen,

2
@yurenchen - Pensa a trapcome usare exception handlingper ottenere il cambiamento nel flusso del codice. In questo caso, la trap sta rilevando tutto ciò che provoca lo script EXIT, che viene attivato invocando il comando inesistente goto. A proposito: l'argomento goto trappotrebbe essere qualsiasi cosa, goto ignoredperché è quello gotoche causa l'USCITA e 2>/dev/nullnasconde il messaggio di errore che dice che lo script è in uscita.
Jesse Chisholm,

2

Questa è una piccola correzione della sceneggiatura di Judy Schmidt messa a punto da Hubbbitus.

Inserire etichette senza escape nello script era problematico sulla macchina e causava un arresto anomalo. Questo è stato abbastanza facile da risolvere aggiungendo # per sfuggire alle etichette. Grazie ad Alexej Magura e access_granted per i loro suggerimenti.

#!/bin/bash
# include this boilerplate
function goto {  
label=$1
cmd=$(sed -n "/$#label#:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}

start=${1:-"start"}

goto $start

#start#
echo "start"
goto bing

#boom#
echo boom
goto eof

#bang#
echo bang
goto boom

#bing#
echo bing
goto bang

#eof#
echo "the end mother-hugger..."

Questa copia incolla non funziona => c'è ancora un jumpto.
Alexis Paques,

Cosa significa? Cosa non funziona? Potresti essere più specifico?
thebunnyrules il

Ovviamente ho provato il codice! Ho testato in 5 o 6 condizioni diverse prima di pubblicare tutto con successo. In che modo il codice ha avuto esito negativo, quale messaggio di errore o codice?
thebunnyrules l'

Qualunque uomo. Voglio aiutarti, ma sei davvero vago e poco comunicativo (per non parlare dell'insulto con quella domanda "l'hai provato"). Che cosa significa "non hai incollato correttamente"? Se ti riferisci a "/ $ # label #: / {: a; n; p; ba};" parte, sono molto consapevole che è diverso. L'ho modificato per il nuovo formato di etichetta (aka # etichetta # vs: etichetta in un'altra risposta che in alcuni casi era un arresto anomalo dello script). Hai qualche problema valido con la mia risposta (come crash di script o sintassi errata) o hai semplicemente -1 la mia risposta perché hai pensato "Non so come tagliare e incollare"?
thebunnyrules

1
@thebunnyrules Ho riscontrato lo stesso problema con te ma l' ho risolto in modo diverso +1 per la tua soluzione!
Fabby,

2

Una semplice ricerca ricercabile per l'uso di commentare i blocchi di codice durante il debug.

GOTO=false
if ${GOTO}; then
    echo "GOTO failed"
    ...
fi # End of GOTO
echo "GOTO done"

Il risultato è-> GOTO fatto


1

Ho scoperto un modo per farlo usando le funzioni.

Diciamo, per esempio, si hanno 3 scelte: A, B, e C. Aed Besegui un comando, ma Cti dà più informazioni e ti riporta di nuovo al prompt originale. Questo può essere fatto usando le funzioni.

Si noti che poiché il conteggio delle righe function demoFunctionsta semplicemente impostando la funzione, è necessario chiamare demoFunctiondopo quello script in modo che la funzione venga effettivamente eseguita.

Puoi facilmente adattarlo scrivendo più altre funzioni e chiamandole se hai bisogno di " GOTO" un altro posto nello script della shell.

function demoFunction {
        read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand

        case $runCommand in
            a|A) printf "\n\tpwd being executed...\n" && pwd;;
            b|B) printf "\n\tls being executed...\n" && ls;;
            c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;;
        esac
}

demoFunction
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.