Gibt es eine "goto" -Anweisung in bash?


204

Gibt es eine "goto" -Anweisung in bash? Ich weiß, dass es als schlechte Praxis angesehen wird, aber ich brauche speziell "goto".


4
Nein, es gibt keine gotoBash (zumindest steht es command not foundfür mich). Warum? Wahrscheinlich gibt es einen besseren Weg, dies zu tun.
Niklas B.

153
Er kann seine Gründe haben. Ich habe diese Frage gefunden, weil ich möchte, dass eine gotoAnweisung viel Code zum Debuggen eines großen Skripts überspringt, ohne eine Stunde auf die Ausführung verschiedener nicht zusammenhängender Aufgaben zu warten. Ich würde gotoim Produktionscode sicherlich kein a verwenden , aber zum Debuggen meines Codes würde dies mein Leben unendlich erleichtern und es wäre leichter zu erkennen, wenn es darum geht, ihn zu entfernen.
Karl Nicoll

30
@delnan Aber kein goto zu haben kann einige Dinge komplizierter machen. Es gibt tatsächlich Anwendungsfälle.
glglgl

70
Ich habe diesen Mythos satt! Es ist nichts falsch mit goto! Alles, was Sie schreiben, wird schließlich gehe. Im Assembler gibt es nur goto. Ein guter Grund, goto in höheren Programmiersprachen zu verwenden, ist beispielsweise das saubere und lesbare Herausspringen aus verschachtelten Schleifen.
Alexander Czutro

25
"Avo goto" ist eine gute Regel. Wie jede Regel sollte es in drei Phasen gelernt werden. Erstens: Befolgen Sie die Regel, bis es zur zweiten Natur geworden ist. Zweitens lernen Sie, die Gründe für die Regel zu verstehen. Drittens lernen Sie gute Ausnahmen von der Regel, basierend auf einem umfassenden Verständnis der Einhaltung der Regel und der Gründe für die Regel. Vermeiden Sie es, die obigen Schritte zu überspringen, da dies mit "goto" vergleichbar wäre. ;-)
Jeff Learman

Antworten:


75

Nein, da ist kein; siehe §3.2.4 „Compound - Befehle“ in dem Bash - Referenzhandbuch für Informationen über die Kontrollstrukturen , die tun exist. Beachten Sie insbesondere die Erwähnung von breakund continue, die nicht so flexibel sind wie goto, aber in Bash flexibler sind als in einigen Sprachen, und Ihnen möglicherweise dabei helfen, das zu erreichen, was Sie wollen. (Was auch immer du willst ...)


9
Könnten Sie "flexibler in Bash als in einigen Sprachen" erweitern?
user239558

21
@ user239558: In einigen Sprachen können Sie nur zur breakoder continuevon der innersten Schleife gelangen, während Sie in Bash angeben können, wie viele Ebenen der Schleife übersprungen werden sollen. (Und selbst bei Sprachen, die es Ihnen ermöglichen, zu breakoder continuevon beliebigen Schleifen zu gelangen, erfordern die meisten, dass diese statisch ausgedrückt werden - z. B. break foo;aus der mit Schleife gekennzeichneten Schleife ausbrechen foo-, während sie in Bash dynamisch ausgedrückt werden - z. B. break "$foo"aus $fooSchleifen ausbrechen . )
Ruakh

Ich würde empfehlen, Funktionen mit den erforderlichen Funktionen zu definieren und sie aus anderen Teilen des Codes zu kalibrieren.
Blmayer

123

Wenn Sie damit einen Teil eines großen Skripts zum Debuggen überspringen (siehe Karl Nicolls Kommentar), könnte false eine gute Option sein (nicht sicher, ob "false" immer verfügbar ist, für mich in / bin / false) ::

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

if false; then

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

fi

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

Die Schwierigkeit tritt auf, wenn es Zeit ist, Ihren Debugging-Code herauszureißen. Das "wenn falsch" -Konstrukt ist ziemlich einfach und einprägsam, aber wie finden Sie die passende Fi? Wenn Ihr Editor das Blockieren des Einrückens zulässt, können Sie den übersprungenen Block einrücken (dann möchten Sie ihn zurücksetzen, wenn Sie fertig sind). Oder ein Kommentar zur Fi-Zeile, aber es müsste etwas sein, an das Sie sich erinnern werden, von dem ich vermute, dass es sehr programmiererabhängig sein wird.


6
Ja falseist immer verfügbar. Wenn Sie jedoch einen Codeblock haben, den Sie nicht ausführen möchten, kommentieren Sie ihn einfach aus. Oder löschen Sie es (und schauen Sie in Ihrem Versionsverwaltungssystem nach, wenn Sie es später wiederherstellen müssen).
Keith Thompson

1
Wenn der Codeblock zu lang ist, um eine Zeile nach der anderen mühsam zu kommentieren, lesen Sie diese Tricks. stackoverflow.com/questions/947897/… Diese helfen jedoch keinem Texteditor, den Anfang mit dem Ende abzugleichen , sodass sie keine große Verbesserung darstellen.
Camille Goudeseune

4
"if false" ist oft weitaus besser als das Auskommentieren von Code, da dadurch sichergestellt wird, dass der beiliegende Code weiterhin ein gesetzlicher Code ist. Die beste Entschuldigung für das Auskommentieren von Code ist, wenn er wirklich gelöscht werden muss, aber es gibt etwas, an das man sich erinnern muss - es ist also nur ein Kommentar und kein "Code" mehr.
Jeff Learman

1
Ich benutze den Kommentar '#if false;'. Auf diese Weise kann ich einfach danach suchen und sowohl den Anfang als auch das Ende des Debug-Entfernungsabschnitts finden.
Jon

Diese Antwort sollte nicht positiv bewertet werden, da sie in keiner Weise auf die Frage des OP antwortet.
Viktor Joras

54

Es kann in der Tat für einige Debug- oder Demonstrationsanforderungen nützlich sein.

Ich fand die Bob Copeland-Lösung 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

Ergebnisse in:

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

35
"Mein Bestreben, Bash wie Assemblersprache aussehen zu lassen, rückt immer näher." - Beeindruckend. Einfach wow.
Brian Agnew

5
Das einzige, was ich ändern würde, ist, es so zu gestalten, dass Beschriftungen so beginnen : start:, dass sie keine Syntaxfehler sind.
Alexej Magura

5
Wäre besser, wenn Sie das tun würden: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') mit Labels, die wie folgt beginnen: # Start: => Dies würde
Skriptfehler

2
Hm, in der Tat ist es höchstwahrscheinlich mein Fehler. Ich habe den Beitrag bearbeitet. Danke dir.
Hubbitus

2
set -xhilft zu verstehen, was los ist
John Lin

30

Sie können casein bash verwenden, um ein goto zu simulieren:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

produziert:

bar
star

4
Beachten Sie, dass dies erforderlich ist bash v4.0+. Es handelt sich jedoch nicht um eine allgemeine, gotosondern um eine Durchfalloption für die caseAnweisung.
mklement0

3
Ich denke, das sollte die Antwort sein. Ich habe ein echtes Bedürfnis zu gehen, um die Wiederaufnahme der Ausführung eines Skripts von einer gegebenen Anweisung zu unterstützen. Dies ist in jeder Hinsicht, aber Semantik, Goto, Semantik und syntaktischer Zucker sind niedlich, aber nicht unbedingt notwendig. tolle Lösung, IMO.
Nathan g

1
@nathang, ob es die Antwort ist, hängt davon ab, ob Ihr Fall mit der Teilmenge des allgemeinen Falls übereinstimmt, nach dem das OP gefragt hat. Leider fragt die Frage nach dem allgemeinen Fall, was diese Antwort zu eng macht, um richtig zu sein. (Ob diese Frage aus diesem Grund als zu weit gefasst geschlossen werden sollte , ist eine andere Diskussion).
Charles Duffy

1
goto ist mehr als nur auswählen. goto feature bedeutet, unter bestimmten Bedingungen zu Orten springen zu können und sogar einen schleifenartigen Fluss zu erzeugen ...
Sergio Abreu

19

Wenn Sie ein Bash-Skript testen / debuggen und einfach einen oder mehrere Codeabschnitte überspringen möchten, ist dies eine sehr einfache Methode, die auch später leicht zu finden und zu entfernen ist (im Gegensatz zu den meisten Methoden) oben beschrieben).

#!/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"

Um Ihr Skript wieder normal zu machen, löschen Sie einfach alle Zeilen mit GOTO.

Wir können diese Lösung auch verschönern, indem wir einen gotoBefehl als Alias hinzufügen :

#!/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"

Aliase funktionieren normalerweise nicht in Bash-Skripten, daher benötigen wir den shoptBefehl, um dies zu beheben.

Wenn Sie Ihre aktivieren / deaktivieren möchten goto, benötigen wir ein bisschen mehr:

#!/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"

Dann können Sie dies tun, export DEBUG=TRUEbevor Sie das Skript ausführen.

Die Beschriftungen sind Kommentare und verursachen daher keine Syntaxfehler, wenn Sie unsere goto's deaktivieren (indem Sie gotodas' :'no-op' setzen). Dies bedeutet jedoch, dass wir sie in unseren gotoAnweisungen zitieren müssen .

Wenn gotoSie eine Lösung verwenden, müssen Sie darauf achten, dass der Code, an dem Sie vorbeigehen, keine Variablen festlegt, auf die Sie sich später verlassen. Möglicherweise müssen Sie diese Definitionen an den Anfang Ihres Skripts oder direkt über eine verschieben Ihrer gotoAussagen.


Laurences Lösung ist einfach cool, ohne auf das Gute / Schlechte von goto einzugehen. Du hast meine Stimme bekommen.
Mogens TrasherDK

1
Das ist eher ein Sprung zu, if(false)scheint (für mich) sinnvoller zu sein.
Vesperto

2
Das Zitieren des goto "label" bedeutet auch, dass das here-doc nicht erweitert / ausgewertet wird, was Sie vor einigen schwer zu findenden Fehlern bewahrt (nicht zitiert, es würde unterliegen: Parametererweiterung, Befehlssubstitution und arithmetische Erweiterung, die alle haben können Nebenwirkungen). Sie können dies auch ein wenig optimieren alias goto=":<<"und ganz darauf verzichten cat.
mr.spuratic

Ja, Vesperto, Sie können eine 'if'-Anweisung verwenden, und das habe ich in vielen Skripten getan, aber es wird sehr chaotisch und ist viel schwieriger zu kontrollieren und zu verfolgen, insbesondere wenn Sie Ihre Sprungpunkte häufig ändern möchten.
Laurence Renshaw

12

Obwohl andere bereits klargestellt haben, dass es gotoin bash kein direktes Äquivalent gibt (und die nächstgelegenen Alternativen wie Funktionen, Schleifen und break bereitgestellt haben), möchte ich veranschaulichen, wie die Verwendung eines loop plus breakeine bestimmte Art von goto-Anweisung simulieren kann.

Die Situation, in der ich dies am nützlichsten finde, ist, wenn ich zum Anfang eines Codeabschnitts zurückkehren muss, wenn bestimmte Bedingungen nicht erfüllt sind. Im folgenden Beispiel wird die while-Schleife für immer ausgeführt, bis der Ping keine Pakete mehr an eine Test-IP sendet.

#!/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

Diese Lösung hatte die folgenden Probleme:

  • Entfernt wahllos alle Codezeilen, die mit a enden :
  • Behandelt label:irgendwo in einer Zeile als Etikett

Hier ist eine feste ( shell-checksaubere) Version:


#!/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

Es gibt noch eine weitere Möglichkeit, die gewünschten Ergebnisse zu erzielen: Befehl trap. Es kann zum Beispiel zu Reinigungszwecken verwendet werden.


4

Es gibt keine gotoin Bash.

Hier ist eine schmutzige Problemumgehung, mit trapder nur rückwärts gesprungen wird :)

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

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

Ausgabe:

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

Dies sollte nicht auf diese Weise verwendet werden, sondern nur zu Bildungszwecken. Hier ist, warum dies funktioniert:

trapverwendet die Ausnahmebehandlung, um die Änderung des Codeflusses zu erreichen. In diesem Fall trapfängt das alles ab, was dazu führt, dass das Skript beendet wird. Der Befehl gotoexistiert nicht und löst daher einen Fehler aus, der normalerweise das Skript beendet. Dieser Fehler wird abgefangen trapund 2>/dev/nullverbirgt die Fehlermeldung, die normalerweise angezeigt wird.

Diese Implementierung von goto ist offensichtlich nicht zuverlässig, da jeder nicht vorhandene Befehl (oder jeder andere Fehler auf diese Weise) denselben Trap-Befehl ausführen würde. Insbesondere können Sie nicht auswählen, zu welchem ​​Etikett Sie wechseln möchten.


Grundsätzlich benötigen Sie im realen Szenario keine goto-Anweisungen, sie sind redundant, da zufällige Aufrufe an verschiedene Stellen Ihren Code nur schwer verständlich machen.

Wenn Ihr Code mehrmals aufgerufen wird, sollten Sie die Schleife verwenden und den Workflow ändern, um continueund zu verwenden break.

Wenn sich Ihr Code selbst wiederholt, sollten Sie die Funktion schreiben und so oft aufrufen, wie Sie möchten.

Wenn Ihr Code basierend auf dem Variablenwert in einen bestimmten Abschnitt springen muss, sollten Sie die Verwendung einer caseAnweisung in Betracht ziehen .

Wenn Sie Ihren langen Code in kleinere Teile aufteilen können, sollten Sie ihn in separate Dateien verschieben und über das übergeordnete Skript aufrufen.


Was ist der Unterschied zwischen dieser Form und einer normalen Funktion?
Yurenchen

2
@yurenchen - Stellen Sie sich vor trap, Sie verwenden exception handling, um die Änderung des Codeflusses zu erreichen. In diesem Fall fängt der Trap alles ab, was das Skript dazu veranlasst EXIT, was durch Aufrufen des nicht vorhandenen Befehls ausgelöst wird goto. Übrigens: Das Argument goto trapkann alles sein, goto ignoredda es das ist goto, was den EXIT verursacht, und das 2>/dev/nulldie Fehlermeldung verbirgt, dass Ihr Skript beendet wird.
Jesse Chisholm

2

Dies ist eine kleine Korrektur des von Hubbbitus erstellten Judy Schmidt-Skripts.

Das Einfügen von nicht maskierten Etiketten in das Skript war auf dem Computer problematisch und führte zum Absturz. Dies war leicht zu lösen, indem # hinzugefügt wurde, um den Beschriftungen zu entkommen. Vielen Dank an Alexej Magura und access_granted für ihre Vorschläge.

#!/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..."

Dieses Kopieren und Einfügen funktioniert nicht => Es ist noch ein Jumpto vorhanden.
Alexis Paques

Was bedeutet das? Was funktioniert nicht? Könnten Sie genauer sein?
Thebunnyrules

Natürlich habe ich den Code ausprobiert! Ich habe unter 5 oder 6 verschiedenen Bedingungen getestet, bevor ich alles erfolgreich gepostet habe. Wie ist der Code für Sie fehlgeschlagen, welche Fehlermeldung oder welcher Code?
Thebunnyrules

Was auch immer. Ich möchte Ihnen helfen, aber Sie sind wirklich vage und nicht kommunikativ (ganz zu schweigen von der Beleidigung mit der Frage "Haben Sie es getestet?"). Was bedeutet "Sie haben nicht richtig eingefügt"? Wenn Sie sich auf das "/ $ # label #: / {: a; n; p; ba};" Teil, ich bin mir sehr bewusst, dass es anders ist. Ich habe es für das neue Etikettenformat geändert (auch bekannt als # label # vs: label in einer anderen Antwort, bei der das Skript in einigen Fällen abstürzte). Haben Sie ein gültiges Problem mit meiner Antwort (wie Skriptabsturz oder schlechte Syntax) oder haben Sie einfach meine Antwort -1, weil Sie dachten "Ich weiß nicht, wie man ausschneidet und einfügt"?
Thebunnyrules

1
@thebunnyrules Ich bin auf dasselbe Problem gestoßen wie Sie, habe es aber anders gelöst +1 für Ihre Lösung!
Fabby

2

Eine einfache Suche zum Auskommentieren von Codeblöcken beim Debuggen.

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

Ergebnis ist-> GOTO erledigt


1

Ich habe einen Weg gefunden, dies mithilfe von Funktionen zu tun.

Nehmen wir zum Beispiel, haben Sie 3 Möglichkeiten: A, B, und C. Aund Bführen Sie einen Befehl aus, erhalten CSie jedoch weitere Informationen und gelangen erneut zur ursprünglichen Eingabeaufforderung. Dies kann mit Funktionen erfolgen.

Beachten Sie, dass function demoFunctionSie demoFunctionnach diesem Skript aufrufen müssen , da die Zeile nur die Funktion einrichtet , damit die Funktion tatsächlich ausgeführt wird.

Sie können dies einfach anpassen, indem Sie mehrere andere Funktionen schreiben und aufrufen, wenn Sie eine GOTOandere Stelle in Ihrem Shell-Skript " " benötigen .

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.