Emulazione di un ciclo do-while in Bash


137

Qual è il modo migliore per emulare un ciclo do-while in Bash?

Potrei verificare la condizione prima di entrare nel whileciclo, quindi continuare a ricontrollare la condizione nel ciclo, ma è un codice duplicato. C'è un modo più pulito?

Pseudo codice del mio script:

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

Questo non funziona check_if_file_presentse lanciato dopo il $cutofftempo, e farebbe un po 'di tempo.


Stai cercando l' untilinterruttore?
Michael Gardner,

2
@MichaelGardner untilvaluterà anche la condizione prima di eseguire il corpo del ciclo
Alex

2
Ah, capisco, ho frainteso il tuo dilemma.
Michael Gardner,

Risposte:


216

Due soluzioni semplici:

  1. Esegui il codice una volta prima 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
:è un built-in, equivalente al built-in true. Entrambi "non fanno nulla con successo".
Loxaxs il

2
@loxaxs che è vero per esempio in zsh ma non in Bash. trueè un vero programma mentre :è integrato. Il primo esce semplicemente con 0(e falsecon 1) il secondo non fa assolutamente nulla. Puoi verificare con which true.
Fleshgrinder,

@Fleshgrinder :è ancora utilizzabile al posto di truein Bash. Provalo con while :; do echo 1; done.
Alexej Magura,

2
Non ho mai detto nulla di diverso, solo che truenon è un built-in in Bash. È un programma che si trova di solito in /bin.
Fleshgrinder,

type truein bash (fino alla bash 3.2) ritorna true is a shell builtin. È vero che /bin/trueè un programma; ciò che non è vero su True è che truenon è un builtin. (tl; dr: true è un build bash E un programma)
PJ Eby

152

Posiziona il corpo del loop dopo whilee prima del test. Il corpo reale del whileloop dovrebbe essere un no-op.

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

Invece dei due punti, puoi usarlo continuese lo trovi più leggibile. Puoi anche inserire un comando che verrà eseguito solo tra iterazioni (non prima del primo o dopo l'ultimo), come ad esempio echo "Retrying in five seconds"; sleep 5. Oppure stampa i delimitatori tra i valori:

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

Ho modificato il test per utilizzare le doppie parentesi poiché sembra che tu stia confrontando numeri interi. All'interno di parentesi quadre doppie, operatori di confronto come quelli <=lessicali e daranno risultati errati confrontando, ad esempio, 2 e 10. Tali operatori non lavorano all'interno di parentesi quadre singole.


È equivalente alla linea singola while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? Vale a dire i comandi all'interno della whilecondizione sono effettivamente separati da punti e virgola non da ad esempio &&e raggruppati per {}?
Ruslan,

@Ruslan: le parentesi graffe non sono necessarie. Non dovresti collegare nulla al test all'interno delle doppie parentesi usando &&o ||poiché ciò li rende effettivamente parte del test che controlla il while. A meno che tu non stia usando questo costrutto dalla riga di comando, non lo farei come una riga (in uno script, in particolare) poiché l'intento è illeggibile.
In pausa fino a nuovo avviso.

Sì, non avevo intenzione di usarlo come one-liner: solo per chiarire come sono collegati i comandi nel test. Mi preoccupavo che il primo comando che restituiva un valore diverso da zero potesse rendere falsa l'intera condizione.
Ruslan,

3
@ruslan: No, è l'ultimo valore di ritorno. while false; false; false; true; do echo here; break; doneoutput "qui"
In pausa fino a nuovo avviso.

@thatotherguy: tra capacità è abbastanza bello! Puoi anche usarlo per inserire un delimitatore in una stringa. Grazie!
In pausa fino a nuovo avviso.

2

Possiamo emulare un ciclo do-while in Bash con while [[condition]]; do true; donequesto:

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

Per un esempio Ecco la mia implementazione su come ottenere la connessione ssh nello 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

Fornirà l'output in sequenza come di seguito:

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.