如何在 bash 中一遍又一遍地运行命令直到成功?

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

提问人:J V 提问时间:3/11/2011 最后编辑:Josh CorreiaJ V 更新时间:11/16/2023 访问量:212622

问:

我有一个脚本,想向用户询问一些信息,但在用户填写此信息之前,脚本无法继续。以下是我尝试将命令放入循环中以实现此目的的尝试,但由于某种原因它不起作用:

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

我尝试了 while 循环的许多变体:

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

但我似乎无法让它工作。

bash 命令 while-loop

评论


答:

112赞 Marc B 3/11/2011 #1

您需要改为测试,这是上一个命令的退出状态。 如果一切正常,则以 0 退出,如果 passwd 更改失败(密码错误、密码不匹配等),则退出非零$?passwd

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

使用您的反引号版本,您正在比较 passwd 的输出,这将是类似之类的东西。Enter passwordconfirm password

评论

1赞 Eric 5/16/2014
我喜欢这个,因为它很清楚如何适应相反的情况。例如,运行一个带有非确定性错误的程序,直到它失败
0赞 Gudlaugur Egilsson 1/30/2017
当非零退出代码指示成功时,这就是您需要的。
12赞 coladict 1/3/2019
我认为这可以通过更改第一个命令来稍微改进,如果您有一些冗长而复杂的命令,您不想保留两个版本。passwd/bin/false
0赞 unixandria 4/20/2020
恕我直言,应该是公认的答案。这应该适用于大多数 shell(在 bash、mksh 和 ash 中测试),并且很容易适应这种情况。
2赞 Nicodemuz 7/28/2020
使用输出失败 shellcheck:直接使用例如“if mycmd;”检查退出代码,而不是间接使用 $?。[编号:SC2181]
10赞 kurumi 3/11/2011 #2

您可以使用无限循环来实现此目的:

while true
do
  read -p "Enter password" passwd
  case "$passwd" in
    <some good condition> ) break;;
  esac
done

评论

2赞 carlin.scott 4/28/2018
这是唯一一个不需要将我的多行命令放在函数中的答案。不过,我是在验证命令之后做的,而不是选择大小写。&& break
553赞 Erik 3/11/2011 #3
until passwd
do
  echo "Try again"
done

while ! passwd
do
  echo "Try again"
done

评论

79赞 tig 3/19/2012
oneliner:until passwd; do echo "Try again"; done
32赞 DonGar 2/11/2013
很难按Ctrl-C摆脱这一点。
84赞 Christian 8/24/2013
很容易 Ctr-C 摆脱了这一点: - 您所要做的就是在完成工作后(在给定的两秒内)立即按 Ctr-C。until passwd; do echo "Try again"; sleep 2; doneecho
47赞 Tom 4/25/2014
Ctrl-Z 后跟 Ctrl-C 不能时在此处工作kill %1
14赞 Justin Sane 8/5/2015
@azmeuk:尝试类似的东西(仅限 bash,如果存在该变量,请确保将 count 设置为 0)如果您需要普通 sh,请递增正文中的计数器并使用 [ ]until passwd || (( count++ >= 5 )); do echo "foo"; done
124赞 duckworthd 7/16/2014 #4

详细阐述@Marc B 的回答,

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

是执行非特定于命令的相同操作的好方法。

如果您想将其作为别名执行此操作(向@Cyberwiz致敬):

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

用法:

$ passwd
$ rr

评论

13赞 Johannes Rudolph 9/10/2014
不幸的是,对我不起作用(我找不到“!!”命令)。它应该如何工作?
5赞 JohnEye 1/8/2015
运行上一个命令是一个 bash 技巧。例如,如果您忘记在命令前面写字,您可以简单地以 root 权限运行上一个命令。sudosudo !!
3赞 Kamil Dziedzic 10/26/2017
跑步之间如何睡觉?
0赞 Lord Elrond 10/17/2020
^^ while [ $? -ne 0 ]; do !!; sleep 1; done
1赞 Cyberwiz 1/13/2023
如果您想使用睡眠作为别名执行此操作:alias rr='while [ $? -ne 0 ]; do sleep 1; eval $(history -p !!); done'
2赞 Andrés Rivas 12/10/2016 #5
while [ -n $(passwd) ]; do
        echo "Try again";
done;

评论

7赞 Ray Foss 6/14/2018
这不是办法......您正在否定 PSSWD 的 stdout,然后对其进行一元测试。@duckworthd很好。
2赞 Charles Duffy 2/22/2020
此外,由于没有引号,当有输出但有多个单词,或者是与当前目录中的文件匹配的 glob 表达式时,测试将出现异常行为,等等。
28赞 aclowkay 8/11/2019 #6

如果有人希望有重试限制:

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

评论

4赞 Charles Duffy 2/22/2020
$command不是一个很好的占位符 -- 参见 BashFAQ #50 了解为什么使用字符串来存储命令本质上是不可靠的。
1赞 rufreakde 3/9/2023
我认为在这个例子中$command应该被你的任意命令所取代。不一定用作字符串。我喜欢用<YOUR_COMMAND>来表示用户必须在这里替换一些东西
0赞 sekrett 11/16/2023 #7

如果您想要严格模式()并且上述方法都不起作用,那就有点棘手了。set -e

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