运行长命令列表并在出现问题时显示消息的便捷方法是什么?

Arq*_*wer 33 shell bash

大多数 Linux 指南由“你需要运行command_1,然后command_2,然后command_3”等页面组成。由于我不想浪费时间手动运行所有这些,我宁愿创建一个脚本

command_1
command_2
command_3
Run Code Online (Sandbox Code Playgroud)

并运行一次。但是,通常情况下,某些命令会失败,而我不知道哪些命令失败了。此外,如果早些时候出现故障,通常所有其余命令都没有意义。所以一个更好的脚本应该是这样的

command_1
command_2
command_3
Run Code Online (Sandbox Code Playgroud)

但是它需要编写太多的样板代码,每个命令重复 3 次,而且很有可能我错误地输入了一些大括号。有没有更方便的方法来做最后一个脚本的作用?特别是:

  • 按顺序运行命令
  • 如果任何命令失败则中断
  • 写,哪个命令失败了,如果有的话
  • 允许与命令的正常交互:打印所有输出,并允许从键盘输入,如果命令有任何要求。

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

有两种类型的解决方案:

  • 那些允许从指南复制粘贴命令而无需修改,但它们最终不会打印失败的命令。因此,如果失败的命令产生了很长的输出,您将不得不向上滚动很多行,以查看失败的命令。(所有最佳答案)
  • 那些在最后一行打印失败的命令,但要求您在复制粘贴后修改命令,通过添加引号(John 的回答),或通过添加try语句并将链接的命令拆分为单独的命令(Jasen 的回答) .

你们这些摇滚乐手,但我会暂时搁置这个问题。也许有人知道满足这两种需求的解决方案(在最后一行打印失败的命令并允许复制粘贴命令而不进行修改)。

use*_*001 62

一种选择是将命令放在 bash 脚本中,并以 .bashrc 开头set -e

如果任何命令以非零退出状态退出,这将导致脚本提前终止。

另请参阅有关堆栈溢出的此问题:https : //stackoverflow.com/q/19622198/828193

要打印错误,您可以使用

trap 'do_something' ERR
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这是输出:

$ ./test.sh 
Command that succeeds
ls: cannot access 'non_existent_file': No such file or directory
******* FAILED *******
Run Code Online (Sandbox Code Playgroud)

此外,正如@jick所提到的,请记住,管道的退出状态默认是其中最终命令的退出状态。这意味着如果管道中的非最终命令失败,则不会被set -e. 如果您担心这个问题,要解决这个问题,您可以使用set -o pipefail


正如我的@glenn jackman@Monty Harder所建议的那样,使用函数作为处理程序可以使脚本更具可读性,因为它避免了嵌套引用。由于我们现在正在使用一个函数,我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
Run Code Online (Sandbox Code Playgroud)

输出与上面相同,但脚本的退出状态不同。

  • 将 `do_something` 变成一个函数会更清晰: `trap do_something ERR; do_something() { echo "FAILED" >&2; }`——这也使得在清理代码中做多项事情变得更容易。 (3认同)

小智 20

想要一个不寻常的解决方案?如果已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."
Run Code Online (Sandbox Code Playgroud)

注意:确保 (heh) 像上面一样缩进你的命令,但要使用制表符。

  • +1 很棒的开箱即用的思维/技巧,将 `make` 重新用于另一项任务。可移植性也非常好(尽管 `sh` 几乎总是比 `make` 更容易获得)。 (3认同)

小智 16

我相信这仅适用于 bash,但您可以尝试:

set -o xtrace
set -o errexit
Run Code Online (Sandbox Code Playgroud)

或者,如果你想简洁,

set -ex
Run Code Online (Sandbox Code Playgroud)

这将做两件事:errexit(-e) 将在出现错误时中止脚本,以及xtrace(-x) 将在 bash 执行之前打印每个命令,因此在失败的情况下,您确切地知道它正在执行什么。

一个缺点是它会使输出混乱,但只要您同意,这是一个非常好的解决方案,只需最少的工作。

  • 当我们这样做时,通常还包括set -o pipefail:是个好主意:否则,如果您运行foo | bar,则失败foo将被静默忽略。(警告:它巧妙地更改了管道语句的“退出代码”,因此如果您正在修改其他人的脚本,请谨慎使用。此外,有时您实际上并不关心foo的失败,因此显然您不能使用pipefail在这种情况下。)

  • `xtrace` 和 `errexit` 是 POSIX。`-e` 和 `-x` 甚至是 Bourne(它们来自哪里)。`pipefail` 来自 ksh93,至少也支持 bash、mksh、zsh、busybox 和 yash。 (3认同)

Joh*_*ohn 3

你可以这样做:

$ for f in command_1 command_2 command_3 command_4
do
  $f
  rc=$?
  if [[ 0 != $rc ]]; then echo Failed command: ${f}; break; fi
done
Run Code Online (Sandbox Code Playgroud)

这假设命令没有选项,如果有,则需要将每个命令/选项集括在引号内。

  • 如果任何命令设置和使用变量,可能会变得棘手 - 可能是 shell 注入。 (3认同)