答案摘要(2020 年 1 月 2 日)

答案摘要(2020 年 1 月 2 日)

大多数 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安装,那么您可以将命令列表放入Makefilefor中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

相关内容