340

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.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
J V
  • 11,402
  • 10
  • 52
  • 72

6 Answers6

545
until passwd
do
  echo "Try again"
done

or

while ! passwd
do
  echo "Try again"
done
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Erik
  • 88,732
  • 13
  • 198
  • 189
  • 77
    oneliner: `until passwd; do echo "Try again"; done` – tig Mar 19 '12 at 12:10
  • 32
    Difficult to Ctrl-C out of this. – DonGar Feb 10 '13 at 22:38
  • 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. – Ethan May 23 '13 at 13:43
  • 83
    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. – Christian Aug 23 '13 at 20:14
  • 45
    Ctrl-Z followed by `kill %1` works here when Ctrl-C won't – Tom Apr 24 '14 at 17:36
  • What is the most simple way to keep trying only a few times ? – azmeuk Sep 16 '14 at 09:51
  • 13
    @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 [ ] – Justin Sane Aug 05 '15 at 11:53
  • 2
    After referring to this page many times, I decided to created a generic bash function to retry commands, here it is: https://gist.github.com/felipou/6fbec22c4e04d3adfae5 – felipou Feb 14 '16 at 14:28
122

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
dropsoid
  • 35
  • 5
duckworthd
  • 14,679
  • 16
  • 53
  • 68
112

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.

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • 1
    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 fails – Eric May 15 '14 at 23:54
  • When success is indicated by a non-zero exit code, this is what you need. – Gudlaugur Egilsson Jan 30 '17 at 14:19
  • 12
    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. – coladict Jan 03 '19 at 09:49
  • 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. – unixandria Apr 20 '20 at 14:42
  • 2
    Fails shellcheck with output: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. [SC2181] – Nicodemuz Jul 28 '20 at 05:18
  • Precisely because of the shellcheck warning I prefer the accepted `until` answer – Miguel Sánchez Villafán Jan 13 '21 at 11:08
27

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
aclowkay
  • 3,577
  • 5
  • 35
  • 66
  • 4
    `$command` isn't a great placeholder -- see [BashFAQ #50](https://mywiki.wooledge.org/BashFAQ/050) on why using strings to store commands is innately unreliable. – Charles Duffy Feb 22 '20 at 13:18
  • 1
    I think in this example $command should just be replaced by your arbitrary command. Not necessarily used as string. I like to use to indicate user has to replace something here – rufreakde Mar 09 '23 at 13:42
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
sgonzalez
  • 1,086
  • 9
  • 24
kurumi
  • 25,121
  • 5
  • 44
  • 52
  • 2
    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. – carlin.scott Apr 27 '18 at 21:19
2
while [ -n $(passwd) ]; do
        echo "Try again";
done;
Andrés Rivas
  • 76
  • 1
  • 1
  • 6
    This is not the way to do it... you're negating the stdout of psswd and then doing a unary test on it. @duckworthd is good. – Ray Foss Jun 13 '18 at 18:20
  • 1
    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. – Charles Duffy Feb 21 '20 at 18:34