Émuler une boucle do-while dans Bash

137

Quelle est la meilleure façon d'émuler une boucle do-while dans Bash?

Je pourrais vérifier la condition avant d'entrer dans la whileboucle, puis continuer à vérifier la condition dans la boucle, mais c'est du code dupliqué. Existe-t-il un moyen plus propre?

Pseudo code de mon script:

while [ current_time <= $cutoff ]; do
    check_if_file_present
    #do other stuff
done

Cela ne fonctionne pas check_if_file_presents'il est lancé après l' $cutoffheure, et un do-while le ferait.

Alex
la source
Cherchez-vous le untilcommutateur?
Michael Gardner
2
@MichaelGardner untilévaluera également la condition avant d'exécuter le corps de la boucle
Alex
2
Ah, je vois, j'ai mal compris votre dilemme.
Michael Gardner

Réponses:

216

Deux solutions simples:

  1. Exécutez votre code une fois avant la boucle while

    actions() {
       check_if_file_present
       # Do other stuff
    }
    
    actions #1st execution
    while [ current_time <= $cutoff ]; do
       actions # Loop execution
    done
    
  2. Ou:

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
    
jm666
la source
9
:est un intégré, équivalent au intégré true. Les deux "ne réussissent rien".
loxaxs du
2
@loxaxs qui est vrai par exemple dans zsh mais pas dans Bash. trueest un programme réel alors qu'il :est intégré. Le premier sort simplement avec 0(et falseavec 1) le second ne fait absolument rien. Vous pouvez vérifier avec which true.
Fleshgrinder
@Fleshgrinder :est toujours utilisable à la place de truedans Bash. Essayez-le avec while :; do echo 1; done.
Alexej Magura le
2
Jamais rien dit de différent, seulement ce truen'est pas un intégré dans Bash. C'est un programme généralement trouvé dans /bin.
Fleshgrinder le
type truein bash (tout le chemin du retour à bash 3.2) renvoie true is a shell builtin. C'est vrai que /bin/truec'est un programme; ce qui n'est pas vrai avec true, c'est que ce truen'est pas un builtin. (tl; dr: true est un bash intégré ET un programme)
PJ Eby
152

Placez le corps de votre boucle après whileet avant le test. Le corps réel de la whileboucle doit être un no-op.

while 
    check_if_file_present
    #do other stuff
    (( current_time <= cutoff ))
do
    :
done

Au lieu des deux points, vous pouvez utiliser continuesi vous trouvez cela plus lisible. Vous pouvez également insérer une commande qui ne s'exécutera qu'entre les itérations (pas avant le premier ou après le dernier), comme echo "Retrying in five seconds"; sleep 5. Ou imprimez des délimiteurs entre les valeurs:

i=1; while printf '%d' "$((i++))"; (( i <= 4)); do printf ','; done; printf '\n'

J'ai changé le test pour utiliser des doubles parenthèses puisque vous semblez comparer des entiers. À l'intérieur de doubles crochets, les opérateurs de comparaison tels que <=sont lexicaux et donneront un résultat erroné lors de la comparaison de 2 et 10, par exemple. Ces opérateurs ne fonctionnent pas entre crochets simples.

Suspendu jusqu'à nouvel ordre.
la source
Est-ce équivalent à une seule ligne while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? C'est-à-dire que les commandes à l'intérieur de la whilecondition sont effectivement séparées par des points-virgules et non par exemple &&, et regroupées par {}?
Ruslan
@Ruslan: Les accolades sont inutiles. Vous ne devriez rien lier au test à l'intérieur des doubles parenthèses en utilisant &&ou ||car cela les fait effectivement partie du test qui contrôle le while. À moins que vous n'utilisiez cette construction sur la ligne de commande, je ne le ferais pas en une seule ligne (dans un script, en particulier) car l'intention est illisible.
Suspendu jusqu'à nouvel ordre.
Oui, je n'avais pas l'intention de l'utiliser comme une seule ligne: juste pour clarifier comment les commandes du test sont connectées. Je craignais que la première commande retournant une valeur non nulle puisse rendre la condition entière fausse.
Ruslan
3
@ruslan: Non, c'est la dernière valeur de retour. while false; false; false; true; do echo here; break; donesorties "ici"
pause jusqu'à nouvel ordre.
@thatotherguy: C'est plutôt cool entre les capacités! Vous pouvez également l'utiliser pour insérer un délimiteur dans une chaîne. Merci!
Suspendu jusqu'à nouvel ordre.
2

Nous pouvons émuler une boucle do-while dans Bash avec while [[condition]]; do true; donecomme ceci:

while [[ current_time <= $cutoff ]]
    check_if_file_present
    #do other stuff
do true; done

À titre d'exemple. Voici mon implémentation pour obtenir une connexion ssh dans un script bash:

#!/bin/bash
while [[ $STATUS != 0 ]]
    ssh-add -l &>/dev/null; STATUS="$?"
    if [[ $STATUS == 127 ]]; then echo "ssh not instaled" && exit 0;
    elif [[ $STATUS == 2 ]]; then echo "running ssh-agent.." && eval `ssh-agent` > /dev/null;
    elif [[ $STATUS == 1 ]]; then echo "get session identity.." && expect $HOME/agent &> /dev/null;
    else ssh-add -l && git submodule update --init --recursive --remote --merge && return 0; fi
do true; done

Il donnera la sortie dans l'ordre comme ci-dessous:

Step #0 - "gcloud": intalling expect..
Step #0 - "gcloud": running ssh-agent..
Step #0 - "gcloud": get session identity..
Step #0 - "gcloud": 4096 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /builder/home/.ssh/id_rsa (RSA)
Step #0 - "gcloud": Submodule '.google/cloud/compute/home/chetabahana/.docker/compose' ([email protected]:chetabahana/compose) registered for path '.google/cloud/compute/home/chetabahana/.docker/compose'
Step #0 - "gcloud": Cloning into '/workspace/.io/.google/cloud/compute/home/chetabahana/.docker/compose'...
Step #0 - "gcloud": Warning: Permanently added the RSA host key for IP address 'XXX.XX.XXX.XXX' to the list of known hosts.
Step #0 - "gcloud": Submodule path '.google/cloud/compute/home/chetabahana/.docker/compose': checked out '24a28a7a306a671bbc430aa27b83c09cc5f1c62d'
Finished Step #0 - "gcloud"
Chetabahana
la source