236

What is the best way to emulate a do-while loop in Bash?

I could check for the condition before entering the while loop, and then continue re-checking the condition in the loop, but that's duplicated code. Is there a cleaner way?

Pseudo code of my script:

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

This doesn't perform check_if_file_present if launched after the $cutoff time, and a do-while would.

4
  • Are you looking for the until switch? Commented May 10, 2013 at 19:52
  • 4
    @MichaelGardner until will also evaluate the condition before executing the body of the loop
    – Alex
    Commented May 10, 2013 at 20:09
  • 2
    Ah, I see, I misunderstood your quandry. Commented May 10, 2013 at 20:34
  • Impressive piece of information. Good question and multiple amazing answers.
    – DrBeco
    Commented Feb 6, 2023 at 1:33

5 Answers 5

349

Two simple solutions:

  1. Execute your code once before the while loop

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

    while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
    
6
  • 23
    : is a built in, equivalent to the built in true. They both "do nothing successfully".
    – loxaxs
    Commented Dec 26, 2017 at 3:40
  • 3
    @loxaxs that is true in e.g. zsh but not in Bash. true is an actual program whereas : is built-in. The former simply exits with 0 (and false with 1) the latter does absolutely nothing. You can check with which true. Commented Dec 2, 2019 at 7:01
  • 2
    @Fleshgrinder : is still usable in place of true in Bash. Try it with while :; do echo 1; done. Commented Feb 12, 2020 at 19:05
  • 20
    type true in bash (all the way back to bash 3.2) returns true is a shell builtin. It's true that /bin/true is a program; what's not true about true is that true is not a builtin. (tl;dr: true is a bash builtin AND a program)
    – PJ Eby
    Commented May 11, 2020 at 23:44
  • 4
    @loxaxs, oh comma... Such a detail, yet in the sentence seems critical: "do nothing, successfully".
    – Artfaith
    Commented Nov 9, 2022 at 10:40
185

Place the body of your loop after the while and before the test. The actual body of the while loop should be a no-op.

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

Instead of the colon, you can use continue if you find that more readable. You can also insert a command that will only run between iterations (not before first or after last), such as echo "Retrying in five seconds"; sleep 5. Or print delimiters between values:

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

I changed the test to use double parentheses since you appear to be comparing integers. Inside double square brackets, comparison operators such as <= are lexical and will give the wrong result when comparing 2 and 10, for example. Those operators don't work inside single square brackets.

6
  • 2
    Is it equivalent to single-line while { check_if_file_present; ((current_time<=cutoff)); }; do :; done? I.e. are the commands inside the while condition effectively separated by semicolons not by e.g. &&, and grouped by {}?
    – Ruslan
    Commented Oct 19, 2016 at 19:49
  • 1
    @Ruslan: The curly braces are unnecessary. You shouldn't link anything to the test inside the double parentheses using && or || since that effectively makes them part of the test that controls the while. Unless you're using this construct on the command line, I wouldn't do it as a one-liner (in a script, specifically) since the intent is unreadable. Commented Oct 19, 2016 at 19:59
  • Yeah, I wasn't intending to use it as a one-liner: just to clarify how the commands in the test are connected. I was worrying that first command returning non-zero might render the whole condition false.
    – Ruslan
    Commented Oct 19, 2016 at 20:01
  • 5
    @ruslan: No, it's the last return value. while false; false; false; true; do echo here; break; done outputs "here" Commented Oct 19, 2016 at 20:51
  • 1
    @thatotherguy: That between capability is pretty cool! You could also use it to insert a delimiter in a string. Thanks! Commented Oct 12, 2018 at 18:48
24

This implementation:

  • Has no code duplication
  • Doesn't require extra functions()
  • Doesn't depend on the return value of code in the "while" section of the loop:
do=true
while $do || conditions; do
  do=false
  # your code ...
done

It works with a read loop, too, skipping the first read:

do=true
while $do || read foo; do
  do=false

  # your code ...
  echo $foo
done
4
  • 1
    I like this answer but also the second example of the accepted answer. This method however does not mess up any of Bash keyword semantics, switching the blocks meaning seems sloppy to me. I tested a bit and obviously while/until are hardcoded to ignore set +e (even if you put it in the list) while do obeys both -e/+e as expected.
    – mpe
    Commented Jan 21, 2022 at 22:45
  • Edit: Oh, and also if you happen to be monitoring return codes, you cannot pass that back with while or until (again obviously) but you need do for that as well. (Why is there this 5min edit window. Can't I deserve just 15 minutes of thinking about this.)
    – mpe
    Commented Jan 21, 2022 at 23:02
  • @mpe, Can you expand on what you mean by "if you happen to be monitoring return codes, you cannot pass that back with while or until (again obviously) but you need do for that as well." ?
    – KJ7LNW
    Commented Jan 22, 2022 at 0:43
  • 2
    Well, with this solution a script can use while <condition>; do <body with non-zero result>; done || <do something with> $?; as is normal, the other examples show a flow where you can never get the end-result status as it is in a while condition which treats this non-zero as an implicit break and then discards it.
    – mpe
    Commented Jan 22, 2022 at 1:57
11

We can emulate a do-while loop in Bash with while [[condition]]; do true; done like this:

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

For an example. Here is my implementation on getting ssh connection in bash script:

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

It will give the output in sequence as below:

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"
2
0

There are lots of good answers here, but they all seem to be more complex than they need to be. I believe the simplest way to do this would be to seed the variable being tested so that thee first check passes.

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

In this instance, "maxtime" would be the maximum possible time that it could be. Of course, the exact format/value would depend on what the "current_time" command/script/function generates.

Not the answer you're looking for? Browse other questions tagged or ask your own question.