Emulowanie pętli do-while w Bash

137

Jaki jest najlepszy sposób na emulację pętli do while w Bash?

Mógłbym sprawdzić warunek przed wejściem do whilepętli, a następnie kontynuować ponowne sprawdzanie warunku w pętli, ale to jest zduplikowany kod. Czy istnieje czystszy sposób?

Pseudo kod mojego skryptu:

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

To nie działa, check_if_file_presentjeśli zostanie uruchomione po $cutoffczasie, a zrobi to po chwili.

Alex
źródło
Szukasz untilprzełącznika?
Michael Gardner
2
@MichaelGardner untiloceni również stan przed wykonaniem treści pętli
Alex
2
Ach, widzę, źle zrozumiałem twoje rozterki.
Michael Gardner

Odpowiedzi:

216

Dwa proste rozwiązania:

  1. Wykonaj kod raz przed pętlą while

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

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
jm666
źródło
9
:jest wbudowany, co odpowiada wbudowany true. Oboje „nic nie robią pomyślnie”.
loxaxs
2
@loxaxs, co jest prawdziwe np. w zsh, ale nie w Bash. truejest rzeczywistym programem, ale :jest wbudowany. Były po prostu wychodzi z 0(oraz falsez 1) ten ostatni robi absolutnie nic. Możesz to sprawdzić which true.
Fleshgrinder
@Fleshgrinder :można nadal używać zamiast truew Bash. Wypróbuj z while :; do echo 1; done.
Alexej Magura
2
Nigdy nie powiedziałem niczego innego, tylko trueto nie jest wbudowane w Bash. Jest to program zwykle znajdujący się w /bin.
Fleshgrinder
type truew bash (z powrotem do bash 3.2) zwraca true is a shell builtin. To prawda, że /bin/trueto program; co nie jest prawdą w stosunku do prawdy, trueto nie jest wbudowana. (tl; dr: true to wbudowany bash ORAZ program)
PJ Eby
152

Umieść korpus pętli po whilei przed testem. Rzeczywista treść whilepętli nie powinna być opcją.

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

Zamiast dwukropka możesz użyć, continuejeśli uznasz to za bardziej czytelne. Możesz także wstawić polecenie, które będzie uruchamiane tylko między iteracjami (nie przed pierwszą ani po ostatniej), takie jak echo "Retrying in five seconds"; sleep 5. Lub wypisuj ograniczniki między wartościami:

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

Zmieniłem test, aby używał podwójnych nawiasów, ponieważ wydaje się, że porównujesz liczby całkowite. W podwójnych nawiasach kwadratowych operatory porównania, takie jak, <=są leksykalne i dadzą zły wynik podczas porównywania na przykład 2 i 10. Te operatory nie działają w pojedynczych nawiasach kwadratowych.

Wstrzymano do odwołania.
źródło
Czy jest to odpowiednik jednej linii while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? To znaczy, czy polecenia wewnątrz whilewarunku są skutecznie oddzielone średnikami, a nie np. &&, I pogrupowane według {}?
Rusłan
@Ruslan: Nawiasy klamrowe są niepotrzebne. Nie powinieneś łączyć niczego z testem wewnątrz podwójnych nawiasów, używając &&lub ||ponieważ to skutecznie czyni je częścią testu, który kontroluje while. O ile nie używasz tej konstrukcji w wierszu poleceń, nie zrobiłbym tego jako jednowierszowy (w szczególności w skrypcie), ponieważ zamiar jest nieczytelny.
Wstrzymano do odwołania.
Tak, nie zamierzałem używać tego jako jednej linijki: tylko po to, aby wyjaśnić, w jaki sposób połączone są polecenia w teście. Martwiłem się, że pierwsza komenda zwracająca wartość niezerową może spowodować, że cały warunek będzie fałszywy.
Ruslan
3
@ruslan: Nie, to ostatnia zwrócona wartość. while false; false; false; true; do echo here; break; donewyjścia „tutaj”
wstrzymane do odwołania.
@thatotherguy: To między możliwościami jest całkiem fajne! Możesz go również użyć do wstawienia separatora w ciągu. Dzięki!
Wstrzymano do odwołania.
2

Możemy emulować pętlę do-while w Bash w while [[condition]]; do true; donenastępujący sposób:

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

Dla przykładu. Oto moja implementacja uzyskiwania połączenia ssh w skrypcie 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

To da wynik w kolejności, jak poniżej:

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
źródło