Bash忽略特定命令的错误

751

我正在使用以下选项

set -o pipefail
set -e

在Bash脚本中停止错误执行。我有大约100行要执行的脚本,不想检查每一行脚本的返回代码。

但是对于一个特定的命令,我希望忽略错误。我该怎么办?

12个回答

1224
解决方案:
particular_script || true

示例:

$ cat /tmp/1.sh
particular_script()
{
    false
}

set -e

echo one
particular_script || true
echo two
particular_script
echo three

$ bash /tmp/1.sh
one
two

three永远不会被打印出来。

另外,我想补充一点,当pipefail开启时, 如果管道中的某个命令退出状态为非零(在关闭pipefail时必须是最后一个命令),那么Shell就认为整个管道的退出状态为非零。

$ set -o pipefail
$ false | true ; echo $?
1
$ set +o pipefail
$ false | true ; echo $?
0

31
根据Bash参考手册的解释,当设置-e属性后,如果执行失败的命令是紧接着whileuntil关键字的命令列表中的一部分、在if语句的测试条件中、在由&&||连接的命令列表中(但不包括最后一个命令之后的命令)、在管道中但不是最后一条命令,或者如果通过使用!将命令的返回状态进行反转,则shell不会退出。 - ruakh
3
(ldd $2/bin/* || true) | grep "not found" | wc -l || true - Igor Chubin
1
因为set -o pipefail的存在。当grep找不到匹配项时,它会返回非零退出代码,这足以让shell认为整个管道命令的退出代码也是非零。 - Igor Chubin
@VivekGoel:我在答案中添加了解释。 - Igor Chubin
7
如果你想保留返回值选项(而不退出),可以尝试使用 mycommand && true。这样可以在后续步骤中检查返回代码并以编程方式处理它。 - Ed Ost
显示剩余2条评论

263

在您希望忽略错误的命令后添加|| true即可。


23
我认为需要加上这一点:使用下面的“!”方法会更改响应代码,而本方法可以保持响应代码和错误信息不变。在使用set -e并尝试捕获错误消息时,这一点非常重要,例如:set -e; TEST=$(foo 2>&1 || true); echo $TEST - Spanky
@Spanky,我不太理解你的例子。你使用了set -e,将bash命令未找到的错误重定向到了$TEST。这样怎么能保留响应代码呢? - CervEd
@Spanky,你是想做类似这样的事情吗:set -e; TEST=$(foo 2>&1) || printf "ERROR $?: $TEST\n" - CervEd

201

不要停止并保存退出状态

如果你希望你的脚本在特定命令失败时不停止,并且还希望保存失败命令的错误码:

set -e
EXIT_CODE=0
command || EXIT_CODE=$?
echo $EXIT_CODE

当命令返回0时,EXIT_CODE未设置为零。尽管捕获了非零退出代码。有任何想法是为什么? - Ankita13
2
@Ankita13 因为如果它是零,第一个 command 就成功了,所以需要运行 || 后面的内容。读作:如果 command 失败,则执行 EXIT_CODE=$?。你可能只需执行 command || echo "$?" 或使用 "trap" 进行更详细的调试。参见此处 -> https://dev59.com/0m025IYBdhLWcg3wRTtK#6110446 - tinnick
7
我认为这可能是最好的答案。无论如何,这正是我在寻找的。我不想让shell退出,但我确实想了解错误的情况,以便我可以做出响应。 非常感谢! - nathan g
我曾使用这种技术手动强制停止Gitlab CI作业,因为我运行的命令似乎会挂起,即使退出代码为1,所以最终我自己执行了退出。 - Patrick.SE
set -e 命令会关闭 SSH 会话。 - alper

87

更简洁地说:

! particular_script

根据POSIX规范中关于set -e的说明(重点是我的):

当打开此选项时,如果一个简单命令因Shell错误的某些原因失败或返回一个大于0的退出状态值,并且不是while、until或if关键字后紧随的复合列表的一部分,并且不是AND或OR列表的一部分,并且不是由"!"保留字引导的管道,则shell应立即退出。


22
我的理解是,无论如何 ! 都会防止 shell 退出。当我运行这个脚本时,它显示消息 Still alive!,表明脚本已经完成运行。你看到的行为有所不同吗? - Lily Finley
12
您说得对,它会反转退出状态,但当命令以0或1结束时不会使脚本崩溃。我当时注意力不集中。无论如何,感谢您引用文档,我将我的表达式放入“if”子句中并解决了问题。 - Marboni
这是我认为最好的解决方案,因为在许多情况下,我想忽略particular_script的返回值,但如果particular_script是一个函数,我就不想忽略函数内部语句的返回值。||解决方案将忽略particular_script函数体内的所有返回代码,这通常不是您想要的(https://dev59.com/xWIj5IYBdhLWcg3w4Y2S#37191242)。 - Johannes
虽然在dash中||无法使用,但是这个可以。谢谢! - bernstein
1
这种方法还有一个优点,就是可以通过PIPESTATUS轻松地检索命令的退出状态:${PIPESTATUS[0]} - pjh
显示剩余2条评论

59

与其“返回true”,您还可以使用“noop”或null实用程序(如POSIX规范中所述):,仅仅是“什么都不做”。这样可以节省几个字符。 :)

#!/usr/bin/env bash
set -e
man nonexistentghing || :
echo "It's ok.."

6
尽管“although”的含义不如“true”那么清晰(这可能会让非专业用户感到困惑),但我喜欢它的简洁性。 - David
这个变量不返回任何文本,这可能在CI中很重要。 - kivagant
好的,但是有没有其他更清晰地返回null的方法? - august0490
@august0490 alias null=:,如果你觉得更清晰,你可以使用null代替: - Timo
1
虽然我理解使用例如null代替:可能会更熟悉/自然于其他语言的开发人员,但这在shell脚本中并不正确。Shell脚本实际上并没有返回任何东西(除了错误号码或成功的0),也没有null这样的类型。添加这样的别名可能会让未来的开发人员误入歧途,因为他们可能会想做类似于alias null=:; if [[ null != $nonexistentghing ]]; then echo "It's set! (not really)"; fi这样的事情,但这根本不是shell的工作方式。 - Timo

23

我找到了另一种解决方法:

set +e
find "./csharp/Platform.$REPOSITORY_NAME/obj" -type f -iname "*.cs" -delete
find "./csharp/Platform.$REPOSITORY_NAME.Tests/obj" -type f -iname "*.cs" -delete
set -e

你可以通过 set +e 关闭错误时的失败情况,这将忽略该行后的所有错误。完成后,如果想要脚本在任何错误时再次失败,可以使用 set -e

应用 set +e 后,当找不到文件时,find 不再使整个脚本失败。同时,find 的错误消息仍然会被打印出来,但整个脚本会继续执行。所以如果这导致问题,很容易进行调试。

这对于 CI 和 CD 非常有用(例如在 GitHub Actions 中)。


1
这是在 GitHub Actions 中对我有效的方法。 - paltaa
我很难相信这里的其他解决方案没有起作用。也许你没有使用 Bourne 兼容的 shell? - tripleee
@tripleee,我的说法确实可能会让人感到困惑,所以我已经更新了它。老实说,我不记得其他解决方案为什么对我无效,或者我是否真的尝试了所有解决方案。 - Konard

16

感谢上面提供的简单解决方案:

<particular_script/command> || true

以下结构可用于脚本步骤的附加操作/故障排除和附加流程控制选项:

if <particular_script/command>
then
   echo "<particular_script/command> is fine!"
else
   echo "<particular_script/command> failed!"
   #exit 1
fi

如果需要,我们可以通过exit 1来停止进一步的操作。


14

如果你想防止你的脚本失败并且收集返回码:

command () {
    return 1  # or 0 for success
}

set -e

command && returncode=$? || returncode=$?
echo $returncode

无论命令成功或失败,returncode都会被收集。


你如何在内联中完成这个操作? - James Geddes
“inline”的意思是什么? command && exit $? || exit $?也可以达到同样的效果,但实际上command; exit也可以。 - tripleee

6
output=$(*command* 2>&1) && exit_status=$? || exit_status=$?
echo $output
echo $exit_status

使用此方法创建日志文件的示例
log_event(){
timestamp=$(date '+%D %T') #mm/dd/yy HH:MM:SS
echo -e "($timestamp) $event" >> "$log_file"
}

output=$(*command* 2>&1) && exit_status=$? || exit_status=$?

if [ "$exit_status" = 0 ]
    then
        event="$output"
        log_event
    else
        event="ERROR $output"
        log_event
fi

2

当我使用CLI工具时,我经常用到下面的片段来判断某个资源是否存在,但我并不关心输出结果。

if [ -z "$(cat no_exist 2>&1 >/dev/null)" ]; then
    echo "none exist actually exist!"
fi

1
这似乎是一种非常绕迂且成本不低的方式来表达“if [ -r not_exist]”。 - tripleee

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