Y at-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 des informations sur les structures de contrôle qui existent do. Notez en particulier la mention de break
et continue
, qui ne sont pas aussi flexibles que goto
, mais sont plus flexibles dans Bash que dans certaines langues et peuvent vous aider à atteindre vos objectifs. (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 peut être une bonne option (ne sachant pas si "false" est toujours disponible, il est pour moi 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é survient lorsqu'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 l'indentation, vous pouvez indenter le bloc ignoré (vous voudrez alors le remettre lorsque vous aurez terminé). Ou un commentaire sur la ligne de départ, mais ce serait une chose dont vous vous souviendrez, qui dépend probablement du programmeur.
Cela peut en effet être utile pour certains besoins de débogage ou de démonstration.
J'ai trouvé cette solution 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 case
dans bash pour simuler un goto:
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
produit:
bar
star
Si vous testez/déboguez un script bash et que vous voulez simplement ignorer une ou plusieurs sections de code, voici une méthode très simple, facile à trouver et à supprimer ultérieurement (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 avec GOTO
.
Nous pouvons également personnaliser 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 résoudre ce problème.
Si vous voulez pouvoir activer/désactiver vos goto
's, il nous faut 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 vous désactivez nos goto
(en définissant goto
sur ':
' no-op), mais cela signifie que nous devons les citer dans nos instructions goto
.
Lorsque vous utilisez une solution goto
, veillez à ce que le code que vous sautez ne définisse pas les variables sur lesquelles vous comptez utiliser ultérieurement. Vous devrez peut-être placer ces définitions en haut de votre script ou juste au-dessus. une de vos déclarations goto
.
Bien que d’autres aient déjà précisé qu’il n’existe pas d’équivalent direct goto
dans bash (et fournit les alternatives les plus proches, telles que fonctions, boucles et pause), j’aimerais illustrer comment l’utilisation d’une boucle plus break
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 sera exécutée indéfiniment jusqu'à ce que ping cesse de déposer des paquets sur 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
Il existe une autre possibilité pour obtenir les résultats souhaités: commande trap
. Il peut être utilisé à des fins de nettoyage, par exemple.
Il n'y a pas de goto
dans bash.
Voici une solution de contournement utilisant trap
qui saute seulement 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
Sortie:
$ ./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:
trap
utilise la gestion des exceptions pour effectuer le changement de flux de code. Dans ce cas, la trap
intercepte tout ce qui cause la sortie du script. La commande goto
n’existe pas et génère donc une erreur qui permet normalement de quitter 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 déroutement. En particulier, vous ne pouvez pas choisir quelle étiquette utiliser.
En gros, dans le scénario réel, vous n'avez pas besoin d'instructions goto, elles sont redondantes, car des appels aléatoires à différents endroits rendent votre code difficile à comprendre.
Si votre code est appelé plusieurs fois, envisagez d'utiliser loop et de modifier son flux de travail pour utiliser 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 passer d'une section spécifique à une autre en fonction de la valeur de la variable, envisagez d'utiliser l'instruction case
.
Si vous pouvez séparer votre code long en éléments plus petits, envisagez de le déplacer dans des fichiers séparés et appelez-les à partir du script parent.
Cette solution avait les problèmes suivants:
:
label:
n'importe où sur une ligne en tant qu'étiquetteVoici une version corrigée ( Shell-check
clean):
#!/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
Ceci est une petite correction du scénario de Judy Schmidt proposé par Hubbbitus.
L'ajout d'étiquettes non échappées dans le script posait problème 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"}
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..."
J'ai trouvé un moyen de faire cela en utilisant des fonctions.
Supposons, par exemple, que vous avez 3 choix: A
, B
et C
. A
et B
execute 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 ligne contenant function demoFunction
ne 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 cela en écrivant plusieurs autres fonctions et en les appelant si vous devez "GOTO
" à un autre endroit de 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
Un simple goto consultable pour 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 fait