在Bash脚本中引发错误
1. 概述
当我们运行我们的脚本时,我们不希望它们在没有任何错误 的情况下完成。我们预见到这一点并编写代码以适应这些意外情况。如果出现错误,我们可能希望向用户显示一条消息并退出脚本。除此之外,我们将打印足够的日志以便稍后调试问题。
在本教程中,我们将检查可以引发错误并退出脚本的不同方式。
2. 使用set -e选项
Bash 有这个内置的set 命令可以在 shell 中设置不同的选项。许多选项之一是errexit。仅设置此选项可帮助我们在任何命令返回非零状态时退出脚本 。
让我们看一个例子:
$ cat set.sh
#!/bin/bash
set -e
ls
ls -l nofolder
ls -l
$ ./set.sh
set.sh
ls: cannot access 'nofolder': No such file or directory
$
在上面的脚本中,我们使用了set -e命令来启用errexit选项。从结果中,我们可以看到执行了第一个ls 命令。对于第二个,它打印了该文件夹不可访问的错误,因为nofolder不存在。对于最后一个ls命令,我们看不到结果。这意味着脚本在遇到上一个命令中的错误时退出。
正如我们所看到的,使用一行代码,我们就能够实现我们所需要的。 尽管这更易于实现,但仍有一些注意事项需要注意。让我们来看看其中的一些案例。
2.1. 使用管道
首先,管道的返回状态是最后一条命令的退出状态。因此,**如果我们在管道中有多个命令并且除了最后一个之外都失败了,那么它被视为成功。**因此,脚本不会停止执行。
让我们用一个例子来看看这个:
$ cat set.sh
#!/bin/bash
set -e
ls
ls | ls -l nofolder | ls
ls -l
$ ./set.sh
set.sh
ls: cannot access 'nofolder': No such file or directory
-rwxrwxr-x 1 bluelake bluelake 306 Mar 28 13:00 set.sh
$
如上所示,我们在管道中间有一个失败的ls命令。**即使它打印错误,脚本也不会停在那里。**从结果中,我们可以看到它一直执行到最后一个ls命令。
幸运的是,对于这个问题,有一个简单的解决方案。
接下来,让我们看看如何解决这个问题:
$ cat set.sh
#!/bin/bash
set -e
set -o pipefail
ls
ls | ls -l nofolder | ls
ls -l
$ ./set.sh
set.sh
ls: cannot access 'nofolder': No such file or directory
$
如上所示,**我们使用set命令添加了另一个选项pipefail 。**因此,管道的退出状态将是最后一个以非零状态退出的命令的退出状态。结果,脚本在管道中遇到错误时已停止执行。
2.2. 其他问题
除此之外,在使用此set命令时,还有一些需要注意的场景。
首先,包含在if或while语句中的命令 不会触发退出条件。
其次,&& 或 || 中的命令 操作员不受此错误退出选项的影响。
让我们看一下这些情况的示例:
$ cat set.sh
#!/bin/bash
set -e
echo "And and Or operator:"
ls && ls -l nofolder || ls
echo "While loop:"
while ls -l nofolder; do
echo "Folder exists"
break
done
echo "If condition:"
if ls -l nofolder; then
echo "Folder exists"
fi
ls -l
$ ./set.sh
And and Or operator:
set.sh
ls: cannot access 'nofolder': No such file or directory
set.sh
While loop:
ls: cannot access 'nofolder': No such file or directory
If condition:
ls: cannot access 'nofolder': No such file or directory
total 24
-rwxrwxr-x 1 bluelake bluelake 192 Mar 28 17:34 set.sh
$
我们可以从上面看到,没有任何错误导致脚本退出。我们在整个脚本中放置了echo命令以了解流程。
最后,在某些情况下,它会意外触发此错误条件。
让我们看看下面的这个脚本:
$ cat t.sh
#!/bin/bash
set -e
i=0
echo "Incrementing"
let i++
ls
$ ./t.sh
Incrementing
$
如上面的脚本所示,我们可以看到最后一个ls命令没有执行。**let 命令将变量i的值增加到 1 并返回它。然后,它被解释为最后一个命令的退出代码并触发errexit条件。**因此,脚本停止执行。
同样,还有更多需要注意的情况。如需完整列表,我们可以查看Bash 手册页 。
3. 使用if-else块
这是检查命令结果并引发异常的直接方法。
让我们看一个例子:
$ cat if.sh
#!/bin/bash
if ! ls -l nofolder; then
echo "Folder doesn't exist"
exit 1
fi
ls -l
$ ./if.sh
ls: cannot access 'nofolder': No such file or directory
Folder doesn't exist
$
如上所示,我们**将可能导致错误的命令包装在 if子句中。**然后我们检查该命令的退出代码,记录错误并exit 脚本。
为了使它更好,我们可以将处理部分移到一个单独的函数中。然后我们可以在遇到任何错误时调用该函数并传递错误字符串。
相反,如果我们有一个小脚本并且没有像上面这样的可重用场景,我们可以将其简化如下:
$ cat if.sh
#!/bin/bash
ls -l nofolder || exit 1
ls -l
$ ./if.sh
ls: cannot access 'nofolder': No such file or directory
在这里,我们使用 OR 运算符来识别错误并退出脚本。
3.1. 使用PIPESTATUS
在使用if语句引发异常时,当我们在管道中使用命令时会遇到问题。我们需要知道管道中的任何命令何时失败 。为此,我们可以使用PIPESTATUS 数组。
PIPESTATUS数组将在其特定索引处包含管道中每个命令的退出代码。然后我们可以检查每个命令的退出代码,并在需要时引发错误。
让我们用一个例子来检查一下:
$ cat pipe.sh
#!/bin/bash
ls | grep nofolder | wc -l
result=`echo ${PIPESTATUS[@]} | grep -E '^[0 ]+$'`
if [ "$result" = "" ]; then
echo "Command failed"
exit 1
fi
ls
$ ./pipe.sh
0
Command failed
在上述脚本中,管道中的grep 命令失败。然后,我们在PIPESTATUS变量中获得退出代码。然后我们通过另一个grep命令运行这个变量的内容。在那里,我们检查其中是否有除零或空格以外的任何字符。**如果出现错误,PIPESTATUS变量将有一个非零的退出代码。**因此,结果变量将为空。最后,我们可以检查这种情况,打印错误并退出脚本。
4. 使用trap命令
trap 是一个 shell 内置命令,用于在接收到特定信号时运行命令。在不同的信号中,ERR是我们感兴趣的信号。当任何命令以非零状态码退出时会发出此消息。
让我们看看如何使用trap命令:
$ cat trap.sh
#!/bin/bash
function handler() {
echo "Error"
exit 1
}
trap handler ERR
ls -l nofolder
ls
$ ./trap.sh
ls: cannot access 'nofolder': No such file or directory
Error
$
在上面的代码片段中,我们声明了一个带有exit命令的函数处理程序。**然后,我们使用trap命令将处理函数绑定到ERR信号。之后,我们运行了失败的ls命令。然后这会引发一个ERR信号,该信号会调用处理程序函数。最后,从处理函数中,我们打印一个错误并退出脚本。
这类似于我们之前看到的set -e命令。**同样的警告也适用于此。我们可以查看手册页 以获取完整列表。**但是这里的一个优点是,我们可以在退出脚本之前通过处理程序进行一些内务处理。
但是,set命令也让我们有机会在退出之前运行处理程序。接下来让我们来看看吧。
4.1. 结合set和trap
我们可以结合set和trap命令来运行一个处理程序。
让我们看一个例子:
$ cat combine.sh
#!/bin/bash
set -e
trap "echo 'Trapped'" ERR
ls -l nofolder
ls -l
$ ./combine.sh
ls: cannot access 'nofolder': No such file or directory
Trapped
$
在这里,当使用trap处理程序出现错误时,它将记录一条消息。由于我们设置了errexit选项,当发生错误时它会自动退出脚本。我们可以看到我们没有在trap处理程序中编写exit命令。