大多数 Linux 指南都包含诸如“您需要运行command_1
、然后command_2
、然后command_3
”等页面。由于我不想浪费时间手动运行所有这些内容,所以我宁愿创建一个脚本
command_1
command_2
command_3
并运行一次。但是,通常情况下,某些命令会失败,而我不知道哪些命令失败了。此外,如果早些时候出现故障,那么通常所有其余命令都毫无意义。所以更好的脚本会是这样的
(command_1 && echo OK command_1 || (echo FAILED command_1; false) )
&& (command_2 && echo OK command_2 || (echo FAILED command_2; false) )
&& (command_3 && echo OK command_3 || (echo FAILED command_3; false) )
&& echo DONE
|| echo FAILED
但它需要编写太多的样板代码,每个命令重复 3 次,而且很有可能我会输错一些大括号。有没有更方便的方法来完成最后一个脚本的操作?尤其:
- 按顺序运行命令
- 如果任何命令失败则中断
- 写入,哪个命令失败了(如果有的话)
- 允许与命令进行正常交互:打印所有输出,并允许从键盘输入(如果命令询问任何内容)。
答案摘要(2020 年 1 月 2 日)
有两种类型的解决方案:
- 那些允许从指南中复制粘贴命令而无需修改,但它们最终不会打印失败的命令。因此,如果失败的命令产生了很长的输出,您将不得不向上滚动很多行,以查看哪个命令失败了。 (所有热门答案)
- 那些在最后一行打印失败的命令,但要求您在复制粘贴命令后修改命令,方法是添加引号(John 回答),或添加
try
语句并将链接命令拆分为单独的命令(Jasen 回答) 。
你们真是太棒了,但我会把这个问题暂时搁置一段时间。也许有人知道满足这两种需求的解决方案(在最后一行打印失败的命令并允许复制粘贴命令而不进行修改)。
答案1
一种选择是将命令放入 bash 脚本中,并以set -e
.
如果任何命令以非零退出状态退出,这将导致脚本提前终止。
另请参阅有关堆栈溢出的问题:https://stackoverflow.com/q/19622198/828193
要打印错误,您可以使用
trap 'do_something' ERR
do_something
您将创建用于显示错误的命令在哪里。
下面是一个脚本示例,您可以了解它是如何工作的:
#!/bin/bash
set -e
trap 'echo "******* FAILED *******" 1>&2' ERR
echo 'Command that succeeds' # this command works
ls non_existent_file # this should fail
echo 'Unreachable command' # and this is never called
# due to set -e
这是输出:
$ ./test.sh
Command that succeeds
ls: cannot access 'non_existent_file': No such file or directory
******* FAILED *******
另外,正如提到的@吉克,请记住,管道的退出状态默认为管道的退出状态最终的命令在其中。这意味着如果管道中的非最终命令失败,则不会被set -e
.要解决此问题(如果您担心它),可以使用set -o pipefail
正如我所建议的@格伦·杰克曼和@蒙蒂·哈德,使用函数作为处理程序可以使脚本更具可读性,因为它避免了嵌套引用。由于我们现在无论如何都在使用一个函数,所以我set -e
完全删除了它并exit 1
在处理程序中使用,这也可以使其对某些人来说更具可读性:
#!/bin/bash
error_handler() {
echo "******* FAILED *******" 1>&2
exit 1
}
trap error_handler ERR
echo 'Command that succeeds' # this command works
ls non_existent_file # this should fail
echo 'Unreachable command' # and this is never called
# due to the exit in the handler
尽管脚本的退出状态不同,但输出与上面相同。
答案2
想要一个不寻常的解决方案吗?如果您已经make
安装,那么您可以将命令列表放入Makefile
for中make
来执行。额外的优点:您不必检查是否发生任何错误。make
会自动检查每个命令的返回值。如果它不为零,则配方会因错误而终止。如果您想忽略某些命令的错误,请将它们与|| true
.
示例生成文件:
.PHONY: all
.SILENT:
all:
echo "Started list of commands."
true
echo "Executing a command which will fail, but I want to ignore failure."
false || true
echo "Executing a command which will definitely fail."
false
echo "This code will not be reached."
注意:确保(嘿)像上面一样缩进你的命令,但是用制表符。
答案3
我相信这只适用于 bash,但你可以尝试:
set -o xtrace
set -o errexit
或者,如果你想简洁一点,
set -ex
这将做两件事:errexit
(-e) 将在出现错误时中止脚本,xtrace
(-x) 将在 bash 执行之前打印每个命令,因此在失败的情况下您可以确切地知道它正在执行什么。
一个缺点是它会使输出混乱,但只要您同意,这就是一个非常好的解决方案,只需最少的工作。
- 当我们这样做时,通常最好也包括
set -o pipefail
:否则,如果您运行foo | bar
,失败foo
将被默默地忽略。 (警告:它巧妙地更改了管道语句的“退出代码”,因此如果您正在修改其他人的脚本,请谨慎使用。此外,有时您实际上并不关心foo
的失败,因此显然您不能使用pipefail
在这种情况下。)
答案4
failed=0
try(){
(( failed )) && return
"$@"
ec=$?
if (( ec ))
then
failed=$ec
echo "failed($failed): $*" >&2
fi
}
try something args
try other-thing more args
if (( failed ))
then
echo "something went wrong: see above." >&2
exit 1
fi