it-swarm.com.de

Gibt es eine "goto" -Anweisung in bash?

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

168
kofucii

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

63
ruakh

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.

101
Michael Rusch

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
35
Hubbitus

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
26
Paul Brannan

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.

15

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
11
Seth McCauley

Es gibt noch eine weitere Möglichkeit, ein gewünschtes Ergebnis zu erzielen: Befehl trap. Es kann beispielsweise zu Reinigungszwecken verwendet werden.

6
Serge Roussak

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.

3
kenorb

Diese Lösung hatte folgende Probleme:

  • Entfernt wahllos alle Codezeilen, die auf : enden.
  • Behandelt label: überall in einer Zeile als Beschriftung

Hier 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
1
Tom Hale

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..."
1
thebunnyrules

Ich habe einen Weg gefunden, dies mit Funktionen zu tun.

Angenommen, Sie haben drei Möglichkeiten: A, B und C. A und Bexecute 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
1
cutrightjm

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 

0
ztalbot