Y a-t-il une déclaration “goto” dans bash?

Y a-t-il une déclaration “goto” dans bash? Je sais que c’est considéré comme une mauvaise pratique, mais j’ai besoin spécifiquement de “goto”.

Non, il n’y en a pas; voir §3.2.4 “Commandes composées” dans le Manuel de référence Bash pour plus d’informations sur les structures de contrôle existantes. En particulier, notez la mention de break et de continue , qui ne sont pas aussi flexibles que goto , mais sont plus flexibles dans Bash que dans certaines langues, et peuvent vous aider à obtenir ce que vous voulez. (Quoi que ce soit que vous voulez…)

Si vous l’utilisez pour ignorer une partie d’un script volumineux pour le débogage (voir le commentaire de Karl Nicoll), alors si false est une bonne option (ne savez 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 “if false” est assez simple et mémorable, mais comment trouvez-vous le fi correspondant? Si votre éditeur vous permet de bloquer le retrait, vous pouvez indenter le bloc ignoré (vous voudrez alors le remettre lorsque vous aurez terminé). Ou un commentaire sur la ligne, mais il faudra que vous vous en souveniez, ce qui, je pense, dépendra beaucoup du programmeur.

Cela 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/ 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 

résulte en:

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

Vous pouvez utiliser la case dans bash pour simuler un goto:

 #!/bin/bash case bar in foo) echo foo ;& bar) echo bar ;& *) echo star ;; esac 

produit:

 bar star 

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

La situation dans laquelle 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 pour toujours jusqu’à ce que ping arrête de déposer des paquets dans 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 

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

 #!/bin/bash echo "Run this" cat >/dev/null </dev/null < 

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

Nous pouvons également améliorer cette solution en ajoutant une commande goto 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 de la commande shopt pour y remédier.

Si vous voulez pouvoir activer / désactiver votre 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" 

Vous pouvez ensuite export DEBUG=TRUE avant d'exécuter le script.

Les étiquettes sont des commentaires, donc ne causeront pas d'erreurs de syntaxe si vous désactivez notre goto (en définissant goto sur le ' : ' no-op), mais cela signifie que nous devons les citer dans nos goto .

Chaque fois que vous utilisez une solution quelconque, vous devez faire attention à ce que le code que vous passez ne définisse aucune variable sur laquelle vous vous appuyez plus tard - vous devrez peut-être déplacer ces définitions vers le haut de votre script, ou juste au-dessus. une de vos déclarations.

Il y a une autre capacité à atteindre les résultats souhaités: le trap commande. Il peut être utilisé à des fins de nettoyage, par exemple.

Il n’y a pas de goto dans bash.

Voici une solution de rechange sale en utilisant trap qui ne saute que vers l’arrière 🙂

 #!/bin/bash -e trap ' echo I am sleep 1 echo here now. ' EXIT echo foo goto trap 2> /dev/null echo bar 

Sortie:

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

Cela ne devrait pas être utilisé de cette manière, mais uniquement à des fins éducatives. Voici pourquoi cela fonctionne:

trap utilise la gestion des exceptions pour obtenir la modification du stream de code. Dans ce cas, l’ trap détecte tout ce qui provoque l’exit du script. La commande goto n’existe pas et jette donc une erreur qui quitterait normalement le script. Cette erreur est interceptée avec trap et le 2>/dev/null masque le message d’erreur qui serait normalement affiché.

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 de piège. En particulier, vous ne pouvez pas choisir l’étiquette à utiliser.


Fondamentalement, dans un scénario réel, vous n’avez pas besoin d’instructions goto, elles sont redondantes car les appels aléatoires à différents endroits rendent votre code difficile à comprendre.

Si votre code est invoqué plusieurs fois, envisagez d’utiliser la boucle et de modifier son stream de production pour continue et break .

Si votre code se répète, envisagez d’écrire la fonction et de 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 une instruction case .

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

J’ai trouvé un moyen de faire cela en utilisant des fonctions.

Disons, par exemple, que vous avez 3 choix: A , B et C A et B exécutent une commande, mais C vous donne plus d’informations et vous ramène à l’invite d’origine. Cela peut être fait en utilisant des fonctions.

Notez que puisque la function demoFunction ligne de la ligne demoFunction function demoFunction fait que configurer la fonction, vous devez appeler demoFunction après ce script pour que la fonction soit exécutée.

Vous pouvez facilement adapter ceci en écrivant plusieurs autres fonctions et en les appelant si vous devez ” GOTO ” un autre endroit dans votre script de 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 

Ceci est une petite correction du script Judy Schmidt mis en place par Hubbbitus.

La mise en place d’étiquettes non d’échappement dans le script était problématique sur la machine et provoquait son blocage. C’était 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"} jumpto $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..."