在Bash中模拟do-while循环

222

在Bash中模拟do-while循环的最佳方法是什么?

我可以在进入while循环之前检查条件,然后在循环中继续重新检查条件,但这是重复的代码。有更简洁的方法吗?

我的脚本的伪代码:

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

如果在$cutoff时间之后启动,则此操作不执行 check_if_file_present,而使用do-while则会执行。


你在寻找 until 开关吗? - Mike Gardner
4
until 在执行循环体之前会评估条件。 - Alex
2
啊,我明白了,我误解了你的困境。 - Mike Gardner
令人印象深刻的信息。好问题和多个惊人的答案。 - DrBeco
令人印象深刻的信息。好问题和多个令人惊叹的答案。 - DrBeco
5个回答

337

两个简单的解决方案:

  1. 在 while 循环之前执行你的代码。

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

  3. while : ; do
        actions
        [[ current_time <= $cutoff ]] || break
    done
    

21
“:`”是一个内置的关键字,它等同于内置的关键字“true”,它们都可以“成功地不做任何事情”。 - loxaxs
3
在zsh中,@loxaxs所说是正确的,但在Bash中则不然。true是一个实际的程序,而:是内置的。前者简单地以0退出(而后者则什么都不做)。您可以使用which true进行检查。 - Fleshgrinder
1
@Fleshgrinder:在Bash中,:仍然可以用作true的替代。尝试使用while :; do echo 1; done - Alexej Magura
2
从来没有说过不同的话,只是在Bash中true不是内置程序。它通常可以在/bin目录下找到。 - Fleshgrinder
18
在bash中键入type true(从bash 3.2开始)会返回true is a shell builtin。虽然/bin/true是一个程序,但是关于true的真相是它也是一个内置命令。简而言之,true既是bash的内置命令又是一个程序。 - PJ Eby
3
@loxaxs,哦,逗号...这么一个细节,在句子中却显得至关重要:“无为而治,成功地”。 - Artfaith

183

在测试之后和循环开始之前放置循环体。while 循环的实际循环体应该是一个空操作。


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

如果你觉得使用冒号不够清晰,可以使用continue。你也可以插入一个只在迭代之间(不是第一次或最后一次之前或之后)运行的命令,比如echo "五秒后重试"; sleep 5。或者在值之间打印分隔符:

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

我将测试更改为使用双括号,因为您似乎在比较整数。在双方括号内,诸如<=之类的比较运算符是词法的,在比较2和10等数字时会给出错误的结果。这些运算符不适用于单方括号。


2
这段代码是否等同于单行的 while { check_if_file_present; ((current_time<=cutoff)); }; do :; done?也就是说,while 条件内的命令是否有效地由分号而非 && 分隔,并由 {} 进行分组? - Ruslan
1
@Ruslan:花括号是不必要的。你不应该使用&&||将任何东西链接到双括号内的测试,因为这实际上使它们成为控制while的测试的一部分。除非你在命令行上使用这个结构,否则我不会将其作为单行代码(特别是在脚本中)使用,因为意图不可读。 - Dennis Williamson
没错,我并不想将它用作一行代码:只是为了澄清测试中的命令之间的连接方式。我担心第一个命令返回非零值可能会导致整个条件失败。 - Ruslan
5
不,它是最后一个返回值。while false; false; false; true; do echo here; break; done会输出“here”。 - Dennis Williamson
1
@thatotherguy:那个“_between_”的功能很酷!你还可以用它在字符串中插入分隔符。谢谢! - Dennis Williamson
将要执行的命令放在 while 循环(或 for 循环)的条件块中似乎相当误导人。使用 while true; do check_if_file_present; [[ current_time <= $cutoff ]] || break; done 会使意图更加清晰。 - Dario Seidl

20

这个实现:

  • 没有代码重复
  • 不需要额外的函数()
  • 不依赖于循环中“while”部分的返回值:
do=true
while $do || conditions; do
  do=false
  # your code ...
done

它也可以通过读取循环运行,跳过第一次读取:

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

  # your code ...
  echo $foo
done

我喜欢这个答案,但也喜欢被接受的答案的第二个例子。然而,这种方法不会破坏任何Bash关键字语义,切换块的含义对我来说似乎有些粗糙。我进行了一些测试,显然 while/until 被硬编码为忽略 set +e(即使您将其放入列表中),而 do 则按预期遵守 -e/+e - mpe
编辑:哦,如果你正在监控返回代码,你不能用whileuntil将其传回(显然),但是你也需要使用do。 (为什么只有这5分钟的编辑窗口。我难道不能得到15分钟来思考吗。) - mpe
@mpe,您能详细说明一下“如果您正在监视返回代码,则无法将其与while或until一起传递(显然),但您也需要使用do来实现。”的意思吗? - KJ7LNW
好的,有了这个解决方案,脚本可以像平常一样使用 while <condition>; do <body with non-zero result>; done || <do something with> $?;。其他示例展示了一种流程,在其中你永远无法获取最终结果状态,因为它在 while 条件中将此非零值视为隐式的 break,然后将其丢弃。 - mpe

11
我们可以使用 while [[condition]]; do true; done 在Bash中模拟 do-while 循环,例如:
while [[ current_time <= $cutoff ]]
    check_if_file_present
    #do other stuff
do true; done

以下是我的一个示例,展示如何在 bash 脚本中获取 ssh 连接

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

1
$STATUS 变量在哪里被初始化了? - RonJohn
1
它是通过bash脚本本身进行初始化的。您可以在*如何在提交后自动推送git?*上获取更多详细信息。 - eQ19

0

这里有很多好的回答,但它们似乎都比必要的复杂。我相信最简单的方法是在被测试的变量中设置一个种子,使第一个检查通过。

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

在这种情况下,“maxtime”将是可能的最大时间。当然,确切的格式/值取决于“current_time”命令/脚本/函数生成的内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接