如何基于for循环的结果使该脚本错误退出?


13

我有一个bash脚本,使用set -o errexit该脚本可以在出错时退出整个脚本。
该脚本运行的curl命令有时无法检索想要的文件-但是,发生这种情况时,脚本不会错误退出。

我添加了一个for循环

  1. 暂停几秒钟,然后重试curl命令
  2. false在for循环的底部使用来定义默认的非零退出状态-如果curl命令成功-循环中断并且最后一个命令的退出状态应为零。
#! /bin/bash

set -o errexit

# ...

for (( i=1; i<5; i++ ))
do
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    if [ -f ~/.vim/autoload/pathogen.vim ]
    then
        echo "file has been retrieved by curl, so breaking now..."
        break;
    fi

    echo "curl'ed file doesn't yet exist, so now will wait 5 seconds and retry"
    sleep 5
    # exit with non-zero status so main script will errexit
    false

done

# rest of script .....

问题是当curl命令失败时,循环将重试该命令五次-如果所有尝试均未成功,则for循环完成并且主脚本恢复-而不是触发errexit
如果此curl语句失败,如何使整个脚本退出?

Answers:


18

更换:

done

与:

done || exit 1

如果for循环以非零退出代码退出,这将导致代码退出。

作为琐事,不需要1in exit 1。如果下载失败,则普通exit命令将以最后执行的命令的退出状态退出false(代码= 1)。如果下载成功,则循环的退出代码就是echo命令的退出代码。 echo通常以代码= 0退出,表示成功。在这种情况下,||不会触发并且exit命令不会执行。

最后,请注意set -o errexit可能充满惊喜。有关其优缺点的讨论,请参阅Greg的FAQ#105

文献资料

来自man bash

对于((expr1; expr2; expr3)); 清单; 完成
首先,根据以下在算术评估中所述的规则对算术表达式expr1进行评估。然后重复对算术表达式expr2求值,直到其求值为零。每次expr2计算为非零值时,将执行列表并计算算术表达式expr3。如果省略任何表达式,则其行为就好像其值为1。 返回值是列表中最后一个已执行命令的退出状态,如果任何一个表达式无效,则返回false。[增加了重点]


您认为将truebreak语句放在显式位置并确保循环的退出值是个好主意吗?
RobertL

1
我认为显式胜于隐式。这就是为什么我写的exit 1时候只是简单的exit行之有效的原因。但是,这是一个风格问题,其他人可以有自己的看法。
John1024

1
效果很好!谢谢:)就我个人而言,我会把它exit看成是一个普通的出口-可以自行终止脚本。exit 1 会在我看来是其他进程(例如errexit)的“信号” -它应基于的“结果”终止脚本exit 1。-所以我去了,exit但谢谢您的解释
the_velour_fog

1
如果由于错误而退出脚本,则应致电exit 1。这一点都不影响errexit。它只是告诉调用程序有问题。该false命令包含一个语句:exit(1)。99.9%的Unix命令成功返回0,错误返回非零。你也应该。
RobertL

2

如果已errexit设置,则该false语句应使脚本立即退出。如果curl命令失败,也是一样。

您所编写的示例脚本应在设置errexit curl时首次调用第一个命令失败后退出false

看看它是如何工作的(我使用简写-e来设置errexit

$ ( set -e;  false; echo still here )
$

$ ( set +e;  false; echo still here )
still here
$

因此,如果curl命令执行多次,则说明该脚本没有errexit设置。


1
set -e比这更微妙。在循环中第一个失败的命令之后,它不会退出。您可以通过运行(set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )并注意代码运行false四次来证明自己。有关更多信息set -e,请参见Greg的FAQ#105
John1024

@ John1024谢谢。这个人下来了下来。
RobertL

@ John1024但我想仍然errexit没有找到证据。请将该逻辑应用于问题中的脚本。运行此命令:(set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) 是,使用if while || &&etc 测试返回值不会触发errexit。原始脚本没有||for循环。
RobertL

我只是注意到我没有set -o errexit在示例代码中显示该命令,而是现在添加了该命令-对我来说,退出该命令不会出现错误。我需要false将for作为最后一个命令保留在for循环中,然后使用close循环done || exit [1]-然后效果很好!
the_velour_fog

@RobertL我明白你的意思。
John1024

1

set -o errexit 在循环和子外壳程序中可能会很棘手,因为您必须将方法退回到该过程之外。

打破循环(即使在正常操作中)也被认为是不好的做法。您可以称呼我为老派,在两种情况下更喜欢使用while循环而不是for循环,但是我发现阅读起来更好:

i=1
RET=-1
while [ $i -le 5 ] && [ $RET -ne 0 ]; do
    [ $i -eq 1 ] || sleep 5
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    RET=$?
    i=$((i+1))
done
exit $RET

0

如果errexit设置了if 并且curl命令失败,则脚本在curl命令失败后立即终止。在bash手册中,没有任何提示可以set -e忽略复合命令中单个命令的任何失败返回状态。只有在set -e忽略了上下文的环境中执行复合命令时,才会出现这种情况。
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

尝试修改一下RobertL发布的示例。这会在false命令之后的第一个for-iteration处停止:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )

0

您可以简单地在curl命令中添加--fail选项,这将解决您的问题,如果curl命令失败,脚本将失败并在出错时退出,如果在jenkins管道中使用curl时也非常有用:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.