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.


¿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

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.


¿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' (git@github.com: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"
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.