首页 > 解决方案 > 为什么带有“and”运算符(“&&”)的链式命令不会在启用“errexit”的非零结果上停止?

问题描述

bash v4.4.12在 Debian 上进行了默认设置:

$ bash --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

我更喜欢在我的脚本中使用这些选项:

set -o pipefail
set -o errexit
set -o nounset

它在管道命令 () 的非零结果上停止脚本pipefail,执行退出 ( errexit),并验证未设置的变量 ( nounset)。

我有测试脚本:

set -o pipefail
set -o errexit
set -o nounset
set -o xtrace

return_1() { return 22; }

test_1 () {
  return_1;
  echo 'after';
}

success_1() {
  echo success
}

if [ "${1:-}" == 1 ]; then
  # make "return 1" in the root level
  return_1
fi

if [ "${1:-}" == 2 ]; then
  # run test_1, we will get "return 1" within a function
  test_1
fi

if [ "${1:-}" == 3 ]; then
  # run test_1 and success_1 in chain
  # success_1 MUST NOT be ran because test_1 makes non-zero status
  # in fact, success_1 will be ran =(
  test_1 && success_1
fi

测试。

$ bash /test.sh 1; echo "status: ${?}"
+ '[' 1 == 1 ']'
+ return_1
+ return 22
status: 22

按预期工作。

$ bash /test.sh 2; echo "status: ${?}"
+ '[' 2 == 1 ']'
+ '[' 2 == 2 ']'
+ test_1
+ return_1
+ return 22
status: 22

一切都是对的。行“回声'之后';” 没有打电话。

$ bash /test.sh 3; echo "status: ${?}"
+ '[' 3 == 1 ']'
+ '[' 3 == 2 ']'
+ '[' 3 == 3 ']'
+ test_1
+ return_1
+ return 22
+ echo after
after
+ success_1
+ echo success
success
status: 0

完全不对。:( 1. "echo 'after';" 行已调用。 2. 函数 "success_1"也已调用

真的,在这种情况下发生了什么?

UPD手册参考。

http://man7.org/linux/man-pages/man1/bash.1.html

标签: basherror-handling

解决方案


你掉进了使用set -e. 仔细阅读为什么 set -e(或 set -o errexit,或陷阱 ERR)没有达到我的预期?

来自GNU bash文档set -e

while如果失败的命令是紧跟在oruntil关键字之后的命令列表的一部分、if 语句中的测试的一部分、在&&or||列表中执行的任何命令的一部分,则 shell 不会退出,但最后一个&&or之后的命令除外||

你认为这段代码会发生什么?

#!/usr/bin/env bash
set -e
test -d nosuchdir && echo no dir
echo survived

自己运行它并观察为什么对使用orset -e运行的命令没有影响。您报告的第三起案件也发生了同样的情况。即使该函数返回一个非零退出代码,使用另一个函数作为其中的一部分已强制 shell 忽略选项集。&&||test_1&&errorexit

最好避免set -e并使用您自己添加的错误检查。在这种情况下,在 if 条件中使用函数的返回码并对结果取反

if ! test_1; then
    success_1
fi

还阅读了 Bash 脚本中的引发错误,其中有一些关于如何在 shell 中以最佳方式处理错误的很好的答案。


推荐阅读