Emulando un bucle do-while en Bash

137

¿Cuál es la mejor manera de emular un bucle do-while en Bash?

Podría verificar la condición antes de ingresar al whileciclo, y luego continuar volviendo a verificar la condición en el ciclo, pero ese es un código duplicado. ¿Hay una manera más limpia?

Pseudo código de mi script:

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

Esto no funciona check_if_file_presentsi se inicia después de ese $cutofftiempo, y un do-while lo haría.

Alex
fuente
¿Estás buscando el untilinterruptor?
Michael Gardner
2
@MichaelGardner untiltambién evaluará la condición antes de ejecutar el cuerpo del bucle
Alex
2
Ah, ya veo, no entendí tu quandry.
Michael Gardner

Respuestas:

216

Dos soluciones simples:

  1. Ejecute su código una vez antes del ciclo while

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

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
jm666
fuente
9
:es un empotrado, equivalente al empotrado true. Ambos "no hacen nada con éxito".
loxaxs
2
@loxaxs que es cierto, por ejemplo, en zsh pero no en Bash. truees un programa real mientras que :está integrado. El primero simplemente sale con 0(y falsecon 1) el último no hace absolutamente nada. Puedes consultar con which true.
Fleshgrinder
@Fleshgrinder :todavía se puede usar en lugar de trueen Bash. Prueba con while :; do echo 1; done.
Alexej Magura
2
Nunca dije nada diferente, solo que trueno está integrado en Bash. Es un programa que generalmente se encuentra en /bin.
Fleshgrinder
type trueen bash (todo el camino de regreso a bash 3.2) regresa true is a shell builtin. Es cierto que /bin/truees un programa; Lo que no es cierto acerca de la verdad es que trueno es una construcción. (tl; dr: true es un bash incorporado y un programa)
PJ Eby
152

Coloque el cuerpo de su asa después de whiley antes de la prueba. El cuerpo real del whilebucle debe ser un no-op.

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

En lugar del colon, puede usarlo continuesi lo encuentra más legible. También puede insertar un comando que solo se ejecutará entre iteraciones (no antes del primero o después del último), como echo "Retrying in five seconds"; sleep 5. O imprima delimitadores entre valores:

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

Cambié la prueba para usar paréntesis dobles ya que parece que estás comparando enteros. Dentro de los corchetes dobles, los operadores de comparación como <=son léxicos y darán un resultado incorrecto al comparar 2 y 10, por ejemplo. Esos operadores no trabajan dentro de corchetes individuales.

Pausado hasta nuevo aviso.
fuente
¿Es equivalente a una sola línea while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? Es decir, ¿los comandos dentro de la whilecondición están efectivamente separados por punto y coma, no por ejemplo &&, y agrupados por {}?
Ruslan
@Ruslan: Las llaves no son necesarias. No debe vincular nada a la prueba dentro de los paréntesis dobles usando &&o ||ya que efectivamente los hace parte de la prueba que controla el while. A menos que esté utilizando esta construcción en la línea de comando, no lo haría como una línea (en un script, específicamente) ya que la intención es ilegible.
Pausado hasta nuevo aviso.
Sí, no tenía la intención de usarlo como una línea: solo para aclarar cómo están conectados los comandos en la prueba. Me preocupaba que el primer comando que devuelve un valor distinto de cero podría volver falsa toda la condición.
Ruslan
3
@ruslan: No, es el último valor de retorno. while false; false; false; true; do echo here; break; donesalidas "aquí"
pausa hasta nuevo aviso.
@thatotherguy: ¡Eso entre capacidad es muy bueno! También podría usarlo para insertar un delimitador en una cadena. ¡Gracias!
Pausado hasta nuevo aviso.
2

Podemos emular un bucle do-while en Bash con while [[condition]]; do true; doneeste:

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

Para un ejemplo. Aquí está mi implementación para obtener conexión ssh en 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

Dará la salida en secuencia de la siguiente manera:

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
fuente