Emulando um loop do-while no Bash


137

Qual é a melhor maneira de emular um loop do-while no Bash?

Eu poderia verificar a condição antes de entrar no whileloop e continuar verificando novamente a condição no loop, mas isso é código duplicado. Existe uma maneira mais limpa?

Pseudo código do meu script:

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

Isso não funciona check_if_file_presentse lançado após o $cutofftempo, e um tempo de execução faria.


Você está procurando o untilswitch?
Michael Gardner

2
@MichaelGardner untiltambém irá avaliar a condição antes de executar o corpo do loop
Alex

2
Ah, entendi, eu não entendi seu dilema.
Michael Gardner

Respostas:


216

Duas soluções simples:

  1. Execute seu código uma vez antes do loop while

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

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

9
:é um interno, equivalente ao interno true. Ambos "não fazem nada com sucesso".
Loxaxs

2
@ loxaxs que é verdade, por exemplo, no zsh, mas não no Bash. trueé um programa real, enquanto :está embutido. O primeiro simplesmente sai com 0(e falsecom 1) o último não faz absolutamente nada. Você pode verificar com which true.
Fleshgrinder 02/12/19

O @Fleshgrinder :ainda pode ser usado no lugar do trueBash. Experimente com while :; do echo 1; done.
Alexej Magura

2
Nunca disse nada de diferente, só que isso truenão é incorporado ao Bash. É um programa geralmente encontrado em /bin.
Fleshgrinder

type trueno bash (todo o caminho de volta ao bash 3.2) retorna true is a shell builtin. É verdade que /bin/trueé um programa; o que não é verdade sobre a verdade é que isso truenão é incorporado. (tl; dr: true é um bash embutido E um programa)
PJ Eby

152

Coloque o corpo do seu loop após whilee antes do teste. O corpo real do whileloop deve ser não operacional.

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

Em vez de dois pontos, você pode usar continuese achar mais legível. Você também pode inserir um comando que será executado apenas entre iterações (não antes da primeira ou depois da última), como echo "Retrying in five seconds"; sleep 5. Ou imprima delimitadores entre valores:

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

Alterei o teste para usar parênteses duplos, já que você parece comparar números inteiros. Dentro de colchetes duplos, operadores de comparação como <=os lexicais e fornecerão o resultado errado ao comparar 2 e 10, por exemplo. Esses operadores não trabalham entre colchetes simples.


É equivalente a uma linha while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? Ou seja, os comandos dentro da whilecondição efetivamente separados por ponto-e-vírgula, não por exemplo &&, e agrupados por {}?
Ruslan #

@Ruslan: As chaves não são necessárias. Você não deve vincular nada ao teste dentro dos parênteses duplos usando &&ou, ||pois isso os torna efetivamente parte do teste que controla o while. A menos que você esteja usando essa construção na linha de comando, eu não faria isso como uma linha (em um script, especificamente), pois a intenção é ilegível.
Pausado até novo aviso.

Sim, eu não pretendia usá-lo como uma linha: apenas para esclarecer como os comandos no teste estão conectados. Eu estava preocupado que o primeiro comando retornando diferente de zero pudesse tornar toda a condição falsa.
Ruslan #

3
@ruslan: Não, é o último valor de retorno. while false; false; false; true; do echo here; break; doneoutputs "here"
Pausado até novo aviso.

@thatotherguy: Isso entre capacidade é bem legal! Você também pode usá-lo para inserir um delimitador em uma string. Obrigado!
Pausado até novo aviso.

2

Podemos emular um loop do-while no Bash com o while [[condition]]; do true; doneseguinte:

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

Por exemplo. Aqui está minha implementação sobre como obter conexão ssh no 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

Ele fornecerá a saída em sequência, como abaixo:

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"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.