How can you run a command in bash over and over until success?
Asked Answered
G

7

353

I have a script and want to ask the user for some information, but the script cannot continue until the user fills in this information. The following is my attempt at putting a command into a loop to achieve this but it doesn't work for some reason:

echo "Please change password"
while passwd
do
    echo "Try again"
done

I have tried many variations of the while loop:

while `passwd`
while [[ "`passwd`" -gt 0 ]]
while [ `passwd` -ne 0 ]]
# ... And much more

But I can't seem to get it to work.

Gagarin answered 11/3, 2011 at 14:41 Comment(0)
N
559
until passwd
do
  echo "Try again"
done

or

while ! passwd
do
  echo "Try again"
done
Narcis answered 11/3, 2011 at 14:48 Comment(8)
oneliner: until passwd; do echo "Try again"; doneAbele
Difficult to Ctrl-C out of this.Layer
If you think you're going to need to cancel this, just open up a new instance of your shell (example, type "bash") and, when you want to cancel it, go into another terminal window and kill the newly spawned bash process.Effectually
Easy to Ctr-C out of this: until passwd; do echo "Try again"; sleep 2; done - all you have to do is press Ctr-C right after (within the two seconds given) the echo did it's job.Poltroonery
Ctrl-Z followed by kill %1 works here when Ctrl-C won'tFineness
What is the most simple way to keep trying only a few times ?Penology
@azmeuk: Try something like until passwd || (( count++ >= 5 )); do echo "foo"; done (bash only, make sure to set count to 0 if that varaible exists) If you need this for plain sh, increment the counter in the body and use [ ]Dogear
After referring to this page many times, I decided to created a generic bash function to retry commands, here it is: gist.github.com/felipou/6fbec22c4e04d3adfae5Hilton
N
127

To elaborate on @Marc B's answer,

$ passwd
$ while [ $? -ne 0 ]; do !!; done

Is a nice way of doing the same thing that's not command specific.

If you want to do this as an alias (kudos to @Cyberwiz):

alias rr='while [ $? -ne 0 ]; do eval $(history -p !!); done'

Usage:

$ passwd
$ rr
Norinenorita answered 16/7, 2014 at 1:48 Comment(6)
Doesn't work for me unfortunately (I get '!!' command not found). How is it supposed to work?Fireback
It is a bash trick to run the previous command. For example if you forget to write sudo in front of a command, you can simply do sudo !! to run the previous command with root privileges.Preventive
How to sleep between runs?Hecklau
^^ while [ $? -ne 0 ]; do !!; sleep 1; doneAppositive
adding sleep 1; won't work since it'll give out 0 as exit value and therefore end the while loop prematurelyLuralurch
If you want to do this as an alias, with sleep: alias rr='while [ $? -ne 0 ]; do sleep 1; eval $(history -p !!); done'Iambic
L
115

You need to test $? instead, which is the exit status of the previous command. passwd exits with 0 if everything worked ok, and non-zero if the passwd change failed (wrong password, password mismatch, etc...)

passwd
while [ $? -ne 0 ]; do
    passwd
done

With your backtick version, you're comparing passwd's output, which would be stuff like Enter password and confirm password and the like.

Levo answered 11/3, 2011 at 14:44 Comment(6)
I like this because it's clear how to adapt it for the opposite situation. e.g. run a program with a non-deterministic bug until it failsMeador
When success is indicated by a non-zero exit code, this is what you need.Despond
I think this can be slightly improved upon by changing the first passwd with /bin/false if you have some long and complicated command that you don't want to keep two versions of.Cyprinodont
Should be the accepted answer imho. This should work in most shells (tested in bash, mksh, and ash) and is easily adaptable to the situation.Doting
Fails shellcheck with output: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. [SC2181]Tutankhamen
Precisely because of the shellcheck warning I prefer the accepted until answerGrandpapa
R
30

If anyone looking to have retry limit:

max_retry=5
counter=0
until <YOUR_COMMAND>
do
   sleep 1
   [[ counter -eq $max_retry ]] && echo "Failed!" && exit 1
   echo "Trying again. Try #$counter"
   ((counter++))
done
Ridglea answered 11/8, 2019 at 8:15 Comment(2)
$command isn't a great placeholder -- see BashFAQ #50 on why using strings to store commands is innately unreliable.Cannula
I think in this example $command should just be replaced by your arbitrary command. Not necessarily used as string. I like to use <YOUR_COMMAND> to indicate user has to replace something hereDarky
K
10

You can use an infinite loop to achieve this:

while true
do
  read -p "Enter password" passwd
  case "$passwd" in
    <some good condition> ) break;;
  esac
done
Kurland answered 11/3, 2011 at 14:48 Comment(1)
This is the only answer that didn't require putting my multi-line command in a function. I did && break after my verification command though instead of the select case.Kill
I
2
while [ -n $(passwd) ]; do
        echo "Try again";
done;
Incinerate answered 9/12, 2016 at 21:13 Comment(2)
This is not the way to do it... you're negating the stdout of psswd and then doing a unary test on it. @Norinenorita is good.Inerrable
Moreover, because there isn't quoting, the test is going to misbehave when there is output but it's more than one word, or is a glob expression that matches files in the current directory, or so forth.Cannula
E
0

It becomes a little tricky if you want the strict mode (set -e) and none of above worked.

set -euo pipefail

counter=0
max_attempts=3
while ret=0; <your command> || ret=$?; [ $ret -ne 0 ]
do
  [[ $counter -eq $max_attempts ]] && echo "Command failed"; exit 1
  sleep 2
  echo "Trying again"
  counter=$((counter+1))
done
Ezarra answered 16/11, 2023 at 11:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.