有没有办法让我的 bash 脚本中的函数在任何命令错误时自动运行?

有没有办法让我的 bash 脚本中的函数在任何命令错误时自动运行?

我正在编写一个 shell 脚本,需要执行一堆命令,并且每个命令都依赖于之前的每个命令。如果任何命令失败,整个脚本就会失败,我会调用退出函数。我可以检查每个命令的退出代码,但我想知道是否有可以启用的模式或让 bash 自动执行此操作的方法。

例如,采用以下命令:

cd foo || myfunc
rm a || myfunc
cd bar || myfunc
rm b || myfunc


有没有一种方法可以让我在执行这些命令之前以某种方式向 shell 发出信号,如果其中任何一个命令失败,它应该调用 myfunc,这样我就可以编写一些更清晰的内容,例如:

cd foo
rm a
cd bar
rm b

答案1

你可以使用bash陷阱错误如果任何命令返回大于零的状态,则使脚本退出并在退出时执行您的函数。

就像是:

myfunc() {
  echo 'Error raised. Exiting!'
}

trap 'myfunc' ERR

# file does not exist, causing error
ls asd
echo 123

注意猛击陷阱 ERR隐式set -o errexitor set -eand 不是 POSIX。

如果失败的命令是紧跟在or关键字之后的命令列表的一部分、是在或保留字之后的测试的一部分、在或列表中执行的命令的一部分,或者如果命令的返回状态正在使用反转,则ERR陷阱不会执行。untilwhileifelif&&||!

答案2

已接受答案的(也许)更简单的变化:

  1. 用于set -e 导致一个命令失败而中止列表的执行。
  2. 只需列出您的命令即可。
  3. 使用if- then-else语句来执行错误处理命令。最后一块有点棘手。手表:
设置-e
如果
    命令1                        # 例如,cd foo
    命令2                        # 例如,rm a
    命令3                        # 例如,cd 酒吧
    命令4                        # 例如,rm b
然后
    设置+e
    成功后执行的命令(如果有)
别的
    设置+e
    我的函数
    失败时执行的其他命令(如果有)

棘手的部分是你输入命令 进入那个if部分if- then- else,而不是then部分或else部分。回想起那个if语句的语法

如果 列表;然后 列表;[ 埃利夫 列表;然后 列表;] ... [ 别的 列表;] 菲
   ↑↑↑↑
告诉set -eshell,如果( ) 失败,它不应该继续执行 ( ),依此类推。如果这种情况发生在 shell 脚本最外层的命令上,shell 将退出。然而,由于......是 一个(复合)列表,后面跟着一个cmd1cd foocmd2rm acmd1cmd2cmd3cmd4if,这四个命令中任何一个的失败只会导致整个列表失败 - 这会导致子句else被执行。如果所有四个命令都成功,则then执行该子句。

e无论哪种情况,您应该做的第一件事可能是通过执行以下操作来禁用(关闭)该选项set +e。否则,如果命令myfunc失败,脚本可能会被炸出水面。

set -ePOSIX 规范中指定和描述

答案3

接受你的话”每个命令都依赖于之前的每个命令。如果任何命令失败,整个脚本都会失败“从字面上看,我认为你不需要任何特殊的函数来处理错误。

您所需要做的就是将命令与&&运算符和||运算符链接起来,这正是您所编写的。

例如,如果出现以下情况,该链将断裂并打印“出问题了”任何之前的命令损坏了(bash 从左到右读取)

cd foo && rm a && cd bar && rm b || echo "something went wrong"

真实示例(我创建了 dir foo、文件 a、dir bar 和文件 b 只是为了真实演示):

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm bb || echo "something is wrong"
rm: cannot remove 'bb': No such file or directory
something is wrong #mind the error in the last command

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm aa && cd bar && rm b || echo "something is wrong"
rm: cannot remove 'aa': No such file or directory
something is wrong #mind the error in second command in the row

最后,如果所有命令均已成功执行(退出代码 0),脚本将继续执行:

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm b || echo "something is wrong"
gv@debian:/home/gv/Desktop/PythonTests/foo/bar$ 
# mind that the error message is not printed since all commands were successful.

重要的是要记住,使用 && 时,如果上一个命令以代码 0 退出,则执行下一个命令,这对于 bash 意味着成功。

如果链中的任何命令出错,则命令/脚本/后面的任何内容||将被执行。

仅供记录,如果您需要根据中断的命令执行不同的操作,您也可以使用经典脚本来执行此操作,方法是监视$?报告前一个命令的退出代码的值(如果命令执行成功,则返回零如果命令失败则为其他正数)

例子:

for comm in {"cd foo","rm a","cd bbar","rm b"};do  #mind the error in third command
eval $comm
    if [[ $? -ne 0 ]];then 
        echo "something is wrong in command $comm"
        break
    else 
    echo "command $comm executed succesful"
    fi
done

输出:

command cd foo executed succesfull
command rm a executed succesfull
bash: cd: bbar: No such file or directory
something is wrong in command cd bbar

提示:您可以通过应用来抑制消息“bash: cd: bbar: No such file...”eval $comm 2>/dev/null

相关内容