web-dev-qa-db-fra.com

Y at-il une déclaration "goto" dans bash?

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

168
kofucii

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

63
ruakh

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.

101
Michael Rusch

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

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

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.

15
Laurence Renshaw

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

Il existe une autre possibilité pour obtenir les résultats souhaités: commande trap. Il peut être utilisé à des fins de nettoyage, par exemple.

6
Serge Roussak

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.

3
kenorb

Cette solution avait les problèmes suivants:

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

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

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

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 Bexecute 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
1
cutrightjm

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 

0
ztalbot