Y a-t-il une déclaration «goto» dans bash?

206

Y a-t-il une instruction "goto" dans bash? Je sais que c'est considéré comme une mauvaise pratique, mais j'ai besoin spécifiquement de "goto".

kofucii
la source
4
Non, il n'y a pas gotode bash (au moins ça dit command not foundpour moi). Pourquoi? Il y a de fortes chances qu'il existe une meilleure façon de procéder.
Niklas B.
153
Il peut avoir ses raisons. J'ai trouvé cette question parce que je veux qu'une gotoinstruction saute beaucoup de code pour déboguer un gros script sans attendre une heure pour que diverses tâches non liées se terminent. Je n'utiliserais certainement pas un gotodans le code de production, mais pour déboguer mon code, cela me rendrait la vie infiniment plus facile, et il serait plus facile de repérer quand il s'agissait de le supprimer.
Karl Nicoll
30
@delnan Mais ne pas avoir de goto peut compliquer certaines choses. Il existe en effet des cas d'utilisation.
glglgl
72
J'en ai marre de ce mythe goto! Il n'y a rien de mal avec goto! Tout ce que vous écrivez devient finalement goto. Dans l'assembleur, il n'y a que goto. Une bonne raison d'utiliser goto dans les langages de programmation supérieurs est par exemple de sauter des boucles imbriquées de manière claire et lisible.
Alexander Czutro
25
"Évitez goto" est une grande règle. Comme toute règle, elle doit être apprise en trois phases. Premièrement: suivez la règle jusqu'à ce qu'elle devienne une seconde nature. Deuxièmement, apprenez à comprendre les raisons de la règle. Troisièmement, apprenez les bonnes exceptions à la règle, sur la base d'une compréhension approfondie de la façon de suivre la règle et des raisons de la règle. Évitez de sauter les étapes ci-dessus, ce qui reviendrait à utiliser "goto". ;-)
Jeff Learman

Réponses:

75

Non, il n'y en a pas; voir §3.2.4 « Commandes composées » dans le Bash Manuel de référence pour obtenir des informations sur les structures de contrôle qui font exist. En particulier, notez la mention de breaket continue, qui ne sont pas aussi flexibles que goto, mais sont plus flexibles dans Bash que dans certaines langues, et peuvent vous aider à réaliser ce que vous voulez. (Quoi que vous vouliez ...)

ruakh
la source
10
Pourriez-vous développer "plus flexible dans Bash que dans certaines langues"?
user239558
21
@ user239558: Certaines langues vous autorisent uniquement vers breakou continuedepuis la boucle la plus interne, tandis que Bash vous permet de spécifier le nombre de niveaux de boucle à sauter. (Et même pour les langues qui vous permettent d'accéder à des boucles arbitraires breakou continued'en revenir, la plupart exigent qu'elles soient exprimées statiquement - par exemple, break foo;elles sortiront de la boucle étiquetée foo- tandis que dans Bash, elles sont exprimées dynamiquement - par exemple, break "$foo"elles sortiront des $fooboucles. )
ruakh
Je recommanderais de définir des fonctions avec les fonctionnalités nécessaires et de les caler à partir d'autres parties du code.
blmayer
@ruakh En fait, de nombreuses langues le permettent break LABEL_NAME;, plutôt que le dégoûtant de Bash break INTEGER;.
Sapphire_Brick
1
@Sapphire_Brick: Oui, je l'ai mentionné dans le commentaire auquel vous répondez.
ruakh
125

Si vous l'utilisez pour ignorer une partie d'un grand script de débogage (voir le commentaire de Karl Nicoll), alors si false pourrait être une bonne option (je ne sais pas si "false" est toujours disponible, pour moi, c'est dans / bin / false) :

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

if false; then

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

fi

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

La difficulté vient quand il est temps d'extraire votre code de débogage. La construction "si faux" est assez simple et mémorable, mais comment trouvez-vous le fi correspondant? Si votre éditeur vous permet de bloquer le retrait, vous pouvez mettre en retrait le bloc ignoré (alors vous voudrez le remettre lorsque vous avez terminé). Ou un commentaire sur la ligne fi, mais il faudrait que vous vous en souveniez, ce qui, je le soupçonne, dépendra beaucoup du programmeur.

Michael Rusch
la source
6
Oui falseest toujours disponible. Mais si vous avez un bloc de code que vous ne voulez pas exécuter, commentez-le. Ou supprimez-le (et regardez dans votre système de contrôle de source si vous avez besoin de le récupérer plus tard).
Keith Thompson
1
Si le bloc de code est trop long pour commenter péniblement une ligne à la fois, consultez ces astuces. stackoverflow.com/questions/947897/… Cependant, cela n'aide pas non plus un éditeur de texte à faire correspondre le début à la fin, donc ce n'est pas vraiment une amélioration.
Camille Goudeseune
4
«si faux» est souvent bien meilleur que de commenter le code, car il garantit que le code joint continue d'être du code légal. La meilleure excuse pour commenter le code est quand il doit vraiment être supprimé, mais il y a quelque chose dont il faut se souvenir - c'est donc juste un commentaire, et non plus du "code".
Jeff Learman
1
J'utilise le commentaire '#if false;'. De cette façon, je peux simplement rechercher cela et trouver à la fois le début et la fin de la section de suppression du débogage.
Jon
Cette réponse ne doit pas être votée positivement car elle ne répond en aucune façon à la question du PO.
Viktor Joras
54

Il peut en effet être utile pour certains besoins de débogage ou de démonstration.

J'ai trouvé que la solution de Bob Copeland http://bobcopeland.com/blog/2012/10/goto-in-bash/ élégant:

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

résulte en:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
Hubbitus
la source
36
"Ma quête pour faire ressembler bash au langage d'assemblage se rapproche de plus en plus." - Sensationnel. Juste wow.
Brian Agnew
5
la seule chose que je changerais est de faire en sorte que les étiquettes commencent comme ça : start:afin qu'elles ne soient pas des erreurs de syntaxe.
Alexej Magura
5
Ce serait mieux si vous faisiez cela: cmd = $ (sed -n "/ # $ label: / {: a; n; p; ba};" $ 0 | grep -v ': $') avec des étiquettes commençant par: # start: => cela empêcherait les erreurs de script
access_granted
2
Hm, en effet c'est très probablement mon erreur. J'ai éditer le post. Je vous remercie.
Hubbitus
2
set -xaide à comprendre ce qui se passe
John Lin
30

Vous pouvez utiliser casedans bash pour simuler un goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

produit:

bar
star
Paul Brannan
la source
4
Notez que cela nécessite bash v4.0+. Il ne s'agit cependant pas d'une gotooption générale, mais d'une option de substitution pour la casedéclaration.
mklement0
3
je pense que cela devrait être la réponse. j'ai un réel besoin d'aller à afin de soutenir la reprise de l'exécution d'un script, à partir d'une instruction donnée. c'est, à tous égards, mais sémantique, goto, et la sémantique et les sucres syntaxiques sont mignons, mais pas strictement nécessaires. excellente solution, OMI.
nathan g
1
@nathang, que ce soit la réponse dépend du fait que votre cas arrive ou non avec le sous-ensemble du cas général sur lequel l'OP a demandé. Malheureusement, la question porte sur le cas général, rendant cette réponse trop étroite pour être correcte. (La question de savoir si cette question doit être classée comme trop large pour cette raison est une discussion différente).
Charles Duffy du
1
goto, c'est plus que choisir. La fonction goto signifie être capable de sauter à des endroits selon certaines conditions, créant même une boucle comme un flux ...
Sergio Abreu
19

Si vous testez / déboguez un script bash et que vous souhaitez simplement sauter une ou plusieurs sections de code, voici un moyen très simple de le faire qui est également très facile à trouver et à supprimer plus tard (contrairement à la plupart des méthodes décrit ci-dessus).

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

Pour remettre votre script à la normale, supprimez simplement les lignes contenant GOTO .

On peut aussi embellir cette solution, en ajoutant une gotocommande comme 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"

Les alias ne fonctionnent généralement pas dans les scripts bash, nous avons donc besoin du shopt commande pour résoudre ce problème.

Si vous voulez pouvoir activer / désactiver le vôtre goto, nous avons besoin d'un peu plus:

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

Ensuite, vous pouvez faire export DEBUG=TRUE avant d'exécuter le script.

Les étiquettes sont des commentaires, donc ne causeront pas d'erreurs de syntaxe si nous désactivons les nôtres goto(en définissant gotole :no-op), mais cela signifie que nous devons les citer dans notregoto déclarations.

Chaque fois que vous utilisez un type de gotosolution, vous devez faire attention à ce que le code sur lequel vous sautez ne définisse aucune variable sur laquelle vous vous appuyerez plus tard - vous devrez peut-être déplacer ces définitions en haut de votre script, ou juste au-dessus d'une de vos gotodéclarations.

Laurence Renshaw
la source
Sans entrer dans le bon / mauvais-ness de goto, la solution de Laurence est juste cool. Vous avez obtenu mon vote.
Mogens TrasherDK
1
Cela ressemble plus à un skip-to, un if(false)semble avoir plus de sens (pour moi).
vesperto
2
Citer le "label" goto signifie également que le document ici n'est pas développé / évalué, ce qui vous évite certains bogues difficiles à trouver (non cité, il serait soumis à: expansion des paramètres, substitution de commandes et expansion arithmétique, qui peuvent tous avoir Effets secondaires). Vous pouvez également modifier cela un peu alias goto=":<<"et vous en passer catcomplètement.
mr.spuratic
oui, vesperto, vous pouvez utiliser une instruction `` si '', et je l'ai fait dans de nombreux scripts, mais cela devient très compliqué et est beaucoup plus difficile à contrôler et à suivre, surtout si vous voulez changer fréquemment vos points de saut.
Laurence Renshaw
12

Bien que d'autres aient déjà précisé qu'il n'y a pas d' gotoéquivalent direct dans bash (et fourni les alternatives les plus proches telles que les fonctions, les boucles et les ruptures), je voudrais illustrer comment l'utilisation d'une boucle plus breakpeut simuler un type spécifique de l'instruction goto.

La situation où je trouve cela le plus utile est lorsque je dois revenir au début d'une section de code si certaines conditions ne sont pas remplies. Dans l'exemple ci-dessous, la boucle while s'exécutera indéfiniment jusqu'à ce que le ping arrête de déposer des paquets vers une adresse IP de test.

#!/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
Seth McCauley
la source
7

Cette solution présentait les problèmes suivants:

  • Supprime sans discrimination toutes les lignes de code se terminant par un :
  • Traite label:n'importe où sur une ligne comme une étiquette

Voici une version fixe ( shell-checkpropre):


#!/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
Tom Hale
la source
6

Il y a une capacité supplémentaire pour obtenir les résultats souhaités: la commande trap. Il peut être utilisé à des fins de nettoyage par exemple.

Serge Roussak
la source
4

Il n'y a pas gotode bash.

Voici une solution de contournement sale trapqui ne saute qu'en arrière :)

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

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

Production:

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

Cela ne devrait pas être utilisé de cette façon, mais uniquement à des fins éducatives. Voici pourquoi cela fonctionne:

traputilise la gestion des exceptions pour effectuer le changement de flux de code. Dans ce cas, le trapcapture tout ce qui provoque la sortie du script. La commande goton'existe pas et génère donc une erreur, qui quitterait normalement le script. Cette erreur est détectée trapet 2>/dev/nullmasque le message d'erreur qui devrait normalement s'afficher.

Cette implémentation de goto n'est évidemment pas fiable, car toute commande inexistante (ou toute autre erreur, de cette manière), exécuterait la même commande trap. En particulier, vous ne pouvez pas choisir l'étiquette à utiliser.


Fondamentalement, dans le scénario réel, vous n'avez pas besoin d'instructions goto, elles sont redondantes car les appels aléatoires à différents endroits ne font que rendre votre code difficile à comprendre.

Si votre code est invoqué plusieurs fois, pensez à utiliser la boucle et à modifier son flux de travail pour utiliser continueet break.

Si votre code se répète, pensez à écrire la fonction et à l'appeler autant de fois que vous le souhaitez.

Si votre code doit sauter dans une section spécifique en fonction de la valeur de la variable, envisagez d'utiliser l' caseinstruction.

Si vous pouvez séparer votre code long en morceaux plus petits, envisagez de le déplacer dans des fichiers séparés et appelez-les depuis le script parent.

Kenorb
la source
quelles sont les différences entre cette forme et une fonction normale?
yurenchen
2
@yurenchen - Pensez trapà utiliser exception handlingpour réaliser le changement de flux de code. Dans ce cas, le piège intercepte tout ce qui provoque le script EXIT, qui est déclenché en appelant la commande inexistante goto. BTW: L'argument goto trappeut être n'importe quoi, goto ignoredcar c'est le gotoqui provoque la 2>/dev/nullsortie , et le masque le message d'erreur indiquant que votre script se termine.
Jesse Chisholm
2

Il s'agit d'une petite correction du script Judy Schmidt mis en place par Hubbbitus.

La mise en place d'étiquettes non échappées dans le script a posé problème sur la machine et l'a fait planter. Cela a été assez facile à résoudre en ajoutant # pour échapper aux étiquettes. Merci à Alexej Magura et access_granted pour leurs suggestions.

#!/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..."
thebunnyrules
la source
Ce copier-coller ne fonctionne pas => il y a encore un jumpto.
Alexis Paques
Qu'est-ce que ça veut dire? Qu'est-ce qui ne fonctionne pas? Pourriez-vous être plus précis?
thebunnyrules
Bien sûr, j'ai essayé le code! J'ai testé sous 5 ou 6 conditions différentes avant de poster tous les succès. Comment le code a-t-il échoué pour vous, quel message d'erreur ou code?
thebunnyrules
Peu importe mec. Je veux vous aider, mais vous êtes vraiment vague et peu communicatif (sans parler d'insulter avec cette question "l'avez-vous testé"). Que signifie "vous n'avez pas collé correctement"? Si vous faites référence au "/ $ # label #: / {: a; n; p; ba};" partie, je suis très conscient que c'est différent. Je l'ai modifié pour le nouveau format d'étiquette (aka # label # vs: label dans une autre réponse qui plantait le script dans certains cas). Avez-vous un problème valide avec ma réponse (comme un crash de script ou une mauvaise syntaxe) ou avez-vous simplement -1 ma réponse parce que vous pensiez "Je ne sais pas comment couper et coller"?
thebunnyrules
1
@thebunnyrules J'ai rencontré le même problème que vous mais je l'ai résolu différemment +1 pour votre solution!
Fabby
2

Un simple goto consultable pour l'utilisation de commenter les blocs de code lors du débogage.

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

Le résultat est-> GOTO done

ztalbot
la source
1

J'ai trouvé un moyen de le faire en utilisant des fonctions.

Disons, par exemple, vous avez 3 choix: A, Bet C. Aet Bexécutez une commande, mais Cvous donne plus d'informations et vous ramène à l'invite d'origine. Cela peut être fait en utilisant des fonctions.

Notez que puisque la ligne contenant ne function demoFunctionfait que configurer la fonction, vous devez appeler demoFunctionaprès ce script pour que la fonction s'exécute réellement.

Vous pouvez facilement l'adapter en écrivant plusieurs autres fonctions et en les appelant si vous avez besoin " GOTO" d'un autre emplacement dans votre script 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
cutrightjm
la source