Gibt es eine "goto" -Anweisung in bash? Ich weiß, dass es als schlechte Praxis gilt, aber ich brauche speziell "goto".
Nein, da ist kein; Siehe §3.2.4 "Zusammengesetzte Befehle" im Bash-Referenzhandbuch für Informationen zu den Kontrollstrukturen, die existieren. Beachten Sie insbesondere die Erwähnung von break
und continue
, die nicht so flexibel sind wie goto
, aber in Bash flexibler sind als in einigen Sprachen und können Ihnen dabei helfen, das zu erreichen, was Sie möchten. (Was auch immer Sie wollen ...)
Wenn Sie es verwenden, um einen Teil eines großen Skripts für das Debugging zu überspringen (siehe Kommentar von Karl Nicoll), könnte False eine gute Option sein. :
# ... 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 "if false" -Konstrukt ist ziemlich unkompliziert und einprägsam, aber wie finden Sie die passende fi? Wenn Ihr Editor Ihnen erlaubt, den Einzug zu blockieren, können Sie den übersprungenen Block einrücken (wenn Sie fertig sind, möchten Sie ihn dann zurücksetzen). Oder ein Kommentar zu der Spielzeile, aber es müsste etwas sein, an das Sie sich erinnern werden, von dem ich vermute, dass es sehr stark vom Programmierer abhängt.
Es kann in der Tat für einige Debugging- oder Demonstrationsanforderungen nützlich sein.
Ich fand diese Lösung von 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
ergebnisse in:
$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
Sie können case
in bash verwenden, um einen goto zu simulieren:
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
produziert:
bar
star
Wenn Sie ein bash-Skript testen/debuggen und einfach einen oder mehrere Codeabschnitte vorwärts überspringen möchten, können Sie dies ganz einfach tun. Außerdem ist es sehr einfach, es später zu finden und zu entfernen (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 verbessern, indem Sie den Befehl goto
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 Befehl shopt
, um das Problem zu beheben.
Wenn Sie Ihre goto
aktivieren/deaktivieren möchten, benötigen wir etwas 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 export DEBUG=TRUE
ausführen, bevor Sie das Skript ausführen.
Die Bezeichnungen sind Kommentare, daher werden keine Syntaxfehler verursacht, wenn Sie unsere goto
deaktivieren (indem Sie goto
auf ':
' no-op setzen). Dies bedeutet jedoch, dass wir sie in unseren goto
-Anweisungen zitieren müssen.
Wenn Sie eine beliebige goto
-Lösung verwenden, müssen Sie darauf achten, dass durch den Code, den Sie überspringen, später keine Variablen festgelegt werden, auf die Sie sich verlassen. Möglicherweise müssen Sie diese Definitionen ganz oben in Ihr Skript verschieben eine Ihrer goto
-Anweisungen.
Obwohl andere bereits klargestellt haben, dass es kein direktes goto
-Äquivalent in bash gibt (und die nächsten Alternativen wie Funktionen, Schleifen und Break bereitstellt), möchte ich veranschaulichen, wie die Verwendung einer Schleife plus break
einen bestimmten Typ einer 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. In dem folgenden Beispiel wird die while-Schleife für immer ausgeführt, bis 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
Es gibt noch eine weitere Möglichkeit, ein gewünschtes Ergebnis zu erzielen: Befehl trap
. Es kann beispielsweise zu Reinigungszwecken verwendet werden.
Es gibt keine goto
in der bash.
Hier ist ein schmutziger Workaround mit trap
, der nur rückwärts springt :)
#!/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. Deshalb funktioniert das:
trap
verwendet die Ausnahmebehandlung, um die Änderung des Codeflusses zu erreichen. In diesem Fall fängt die trap
alles ab, was dazu führt, dass das Skript beendet wird. Der Befehl goto
ist nicht vorhanden und gibt daher einen Fehler aus, der das Skript normalerweise beenden würde. Dieser Fehler wird mit trap
abgefangen und der 2>/dev/null
verbirgt die Fehlermeldung, die normalerweise angezeigt wird.
Diese Implementierung von goto ist offensichtlich nicht zuverlässig, da ein nicht vorhandener Befehl (oder ein anderer Fehler auf diese Weise) denselben Trap-Befehl ausführen würde. Insbesondere können Sie nicht auswählen, welches Label Sie verwenden möchten.
Im Grunde benötigen Sie keine GOTO-Anweisungen. Sie sind redundant, da zufällige Anrufe an verschiedene Stellen Ihren Code nur schwer verständlich machen.
Wenn Ihr Code mehrmals aufgerufen wird, sollten Sie die Verwendung von Schleife und den Workflow für continue
und break
in Betracht ziehen.
Wenn Ihr Code sich selbst wiederholt, ziehen Sie in Betracht, die Funktion zu schreiben und sie so oft aufzurufen, wie Sie möchten.
Wenn Ihr Code basierend auf dem Variablenwert in einen bestimmten Abschnitt springen muss, sollten Sie die case
-Anweisung in Betracht ziehen.
Wenn Sie Ihren langen Code in kleinere Teile unterteilen können, sollten Sie ihn in separate Dateien verschieben und vom übergeordneten Skript aus aufrufen.
Diese Lösung hatte folgende Probleme:
:
enden.label:
überall in einer Zeile als BeschriftungHier ist eine feste ( Shell-check
clean) 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
Dies ist eine kleine Korrektur des von Hubbbitus bereitgestellten Judy-Schmidt-Skripts.
Das Einfügen nicht maskierter Labels in das Skript war auf dem Computer problematisch und führte zum Absturz. Dies war leicht genug, um das Problem zu beheben, indem Sie # zum Beenden der Labels hinzufügen. 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..."
Ich habe einen Weg gefunden, dies mit Funktionen zu tun.
Angenommen, Sie haben drei Möglichkeiten: A
, B
und C
. A
und B
execute einen Befehl, aber C
gibt Ihnen weitere Informationen und führt Sie wieder zur ursprünglichen Aufforderung. Dies kann über Funktionen erfolgen.
Da die Zeile, die function demoFunction
enthält, nur die Funktion einrichtet, müssen Sie nach diesem Skript demoFunction
aufrufen, damit die Funktion tatsächlich ausgeführt wird.
Sie können dies leicht anpassen, indem Sie mehrere andere Funktionen schreiben und aufrufen, wenn Sie an einer anderen Stelle in Ihrem Shell-Skript "GOTO
" 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
Ein einfacher durchsuchbarer Spruch zum Kommentieren von Codeblöcken beim Debuggen
GOTO=false
if ${GOTO}; then
echo "GOTO failed"
...
fi # End of GOTO
echo "GOTO done"
Ergebnis ist-> GOTO erledigt