Emulieren einer Do-While-Schleife in Bash


136

Was ist der beste Weg, um eine Do-While-Schleife in Bash zu emulieren?

Ich könnte vor dem Eintritt in die whileSchleife nach der Bedingung suchen und dann die Bedingung in der Schleife erneut überprüfen, aber das ist doppelter Code. Gibt es einen saubereren Weg?

Pseudocode meines Skripts:

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

Dies funktioniert nicht, check_if_file_presentwenn es nach der $cutoffZeit gestartet wird, und eine Pause würde dies tun.


Suchen Sie den untilSchalter?
Michael Gardner

2
@ MichaelGardner untilwird auch die Bedingung bewerten, bevor der Körper der Schleife ausgeführt wird
Alex

2
Ah, ich verstehe, ich habe dein Dilemma falsch verstanden.
Michael Gardner

Antworten:


216

Zwei einfache Lösungen:

  1. Führen Sie Ihren Code einmal vor der while-Schleife aus

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

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done

9
:ist ein eingebautes, entspricht dem eingebauten true. Beide "machen nichts erfolgreich".
Loxaxs

2
@loxaxs das stimmt zB zsh aber nicht in Bash. trueist ein aktuelles Programm, während :es eingebaut ist. Ersteres verlässt einfach mit 0(und falsemit 1) Letzterem tut absolut nichts. Sie können mit überprüfen which true.
Fleischwolf

@Fleshgrinder :ist weiterhin anstelle von truein Bash verwendbar . Probieren Sie es mit while :; do echo 1; done.
Alexej Magura

2
Nie etwas anderes gesagt, nur das trueist kein eingebauter Bash. Es ist ein Programm, das normalerweise in zu finden ist /bin.
Fleischwolf

type truein bash (bis zurück zu bash 3.2) kehrt zurück true is a shell builtin. Es ist wahr, dass dies /bin/trueein Programm ist; Was an wahr nicht wahr ist, trueist, dass es nicht eingebaut ist. (tl; dr: true ist eine eingebaute Bash UND ein Programm)
PJ Eby

152

Platzieren Sie den Körper Ihrer Schleife nach dem whileund vor dem Test. Der eigentliche Körper der whileSchleife sollte ein No-Op sein.

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

Anstelle des Doppelpunkts können Sie verwenden, continuewenn Sie dies besser lesbar finden. Sie können auch einen Befehl einfügen, der nur zwischen Iterationen ausgeführt wird (nicht vor dem ersten oder nach dem letzten), z echo "Retrying in five seconds"; sleep 5. Oder drucken Sie Trennzeichen zwischen Werten:

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

Ich habe den Test so geändert, dass doppelte Klammern verwendet werden, da Sie scheinbar ganze Zahlen vergleichen. In doppelten eckigen Klammern sind Vergleichsoperatoren wie z. B. <=lexikalisch und liefern beispielsweise beim Vergleich von 2 und 10 das falsche Ergebnis. Diese Operatoren arbeiten nicht in eckigen Klammern.


Entspricht es einzeilig while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? Dh sind die Befehle innerhalb der whileBedingung effektiv durch Semikolons getrennt, nicht durch zB &&, und gruppiert durch {}?
Ruslan

@ Ruslan: Die geschweiften Klammern sind unnötig. Sie sollten nichts mit dem Test in den doppelten Klammern verknüpfen, indem Sie &&oder ||weil dies sie effektiv zu einem Teil des Tests macht, der das kontrolliert while. Wenn Sie dieses Konstrukt nicht in der Befehlszeile verwenden, würde ich es nicht als Einzeiler (speziell in einem Skript) ausführen, da die Absicht nicht lesbar ist.
Bis auf weiteres angehalten.

Ja, ich hatte nicht vor, es als Einzeiler zu verwenden: nur um zu klären, wie die Befehle im Test verbunden sind. Ich befürchtete, dass der erste Befehl, der nicht Null zurückgibt, die gesamte Bedingung falsch machen könnte.
Ruslan

3
@ Ruslan: Nein, es ist der letzte Rückgabewert. while false; false; false; true; do echo here; break; doneAusgänge "hier"
Bis auf weiteres angehalten.

@thatotherguy: Das zwischen Fähigkeiten ist ziemlich cool! Sie können es auch verwenden, um ein Trennzeichen in eine Zeichenfolge einzufügen. Vielen Dank!
Bis auf weiteres angehalten.

2

Wir können eine Do-While-Schleife in Bash folgendermaßen emulieren while [[condition]]; do true; done:

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

Zum Beispiel. Hier ist meine Implementierung zum Herstellen einer SSH-Verbindung im Bash-Skript:

#!/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

Die Ausgabe erfolgt nacheinander wie folgt:

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"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.