Эмуляция цикла do-while в Bash

137

Каков наилучший способ эмулировать цикл выполнения в Bash?

Я мог бы проверить условие перед входом в whileцикл, а затем продолжить повторную проверку условия в цикле, но это дублированный код. Есть ли более чистый способ?

Псевдокод моего скрипта:

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

Это не сработает, check_if_file_presentесли запустится через $cutoffнекоторое время, и сделаем так, как хотелось бы.

Alex
источник
Вы ищете untilвыключатель?
Майкл Гарднер
2
@MichaelGardner untilтакже оценит условие перед выполнением тела цикла
Алекс
2
Ах, я понимаю, я неправильно понял ваше затруднение.
Майкл Гарднер

Ответы:

216

Два простых решения:

  1. Выполните ваш код один раз перед циклом while

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

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
    
jm666
источник
9
:является встроенным, эквивалентным встроенному true. Они оба "ничего не делают успешно".
loxaxs
2
@loxaxs, что верно, например, в zsh, но не в Bash. trueэто актуальная программа, тогда :как встроенная. Первый просто выходит с 0falseс 1), последний ничего не делает. Вы можете проверить с which true.
Fleshgrinder
@Fleshgrinder :все еще можно использовать вместо trueBash. Попробуйте это с while :; do echo 1; done.
Алексей Магура
2
Никогда не говорил ничего другого, только то, что trueне встроено в Bash. Эта программа обычно находится в /bin.
Флешгриндер
type trueв bash (вплоть до bash 3.2) возвращается true is a shell builtin. Это правда, что /bin/trueэто программа; что неправда в истине, так это то, что trueона не является встроенной. (tl; dr: true - встроенная программа bash и программа)
PJ Eby
152

Поместите тело вашей петли после whileи до теста. Фактическое тело whileцикла должно быть неактивным.

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

Вместо двоеточия вы можете использовать его, continueесли найдете его более читабельным. Вы также можете вставить команду, которая будет выполняться только между итерациями (не до первой или после последней), например echo "Retrying in five seconds"; sleep 5. Или выведите разделители между значениями:

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

Я изменил тест, чтобы использовать двойные скобки, так как вы, кажется, сравниваете целые числа. Внутри двойных квадратных скобок операторы сравнения, такие как <=лексические, дадут неправильный результат, например, при сравнении 2 и 10. Эти операторы не работают в квадратных скобках.

Приостановлено до дальнейшего уведомления.
источник
Это эквивалентно одной строке while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? Т.е. команды внутри whileусловия эффективно разделены точками с запятой, а не, например &&, и сгруппированы {}?
Руслан
@Ruslan: фигурные скобки не нужны. Вы не должны связывать что-либо с тестом в двойных скобках, используя &&или ||потому что это делает их частью теста, который контролирует while. Если вы не используете эту конструкцию в командной строке, я бы не стал использовать ее как однострочную (особенно в сценарии), поскольку намерение не читается.
Приостановлено до дальнейшего уведомления.
Да, я не собирался использовать его как однострочник: просто чтобы выяснить, как связаны команды в тесте. Я беспокоился, что первая команда, возвращающая ненулевое значение, может сделать все условие ложным.
Руслан
3
@ruslan: Нет, это последнее возвращаемое значение. while false; false; false; true; do echo here; break; doneВывод «здесь»
приостановлен до дальнейшего уведомления.
@thatotherguy: Между возможностями это круто! Вы также можете использовать его для вставки разделителя в строку. Спасибо!
Приостановлено до дальнейшего уведомления.
2

Мы можем эмулировать цикл do-while в Bash while [[condition]]; do true; doneследующим образом:

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

Для примера. Вот моя реализация получения ssh-соединения в 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

Это даст вывод в последовательности, как показано ниже:

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"
Chetabahana
источник