在bash脚本中set -e是什么意思?

2013-10-28 linux bash shell sh

我正在研究脚本从该脚本的Debian存档(.deb)文件解压缩之前执行的preinst文件的内容。

该脚本具有以下代码:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

我的第一个查询是关于这一行的:

set -e

我认为脚本的其余部分非常简单:它检查Debian / Ubuntu软件包管理器是否正在执行安装操作。如果是,它将检查我的应用程序是否刚刚安装在系统上。如果包含,则脚本将显示消息“ MyApplicationName已安装”并结束( return 1表示以“错误”结尾,不是吗?)。

如果用户要求Debian / Ubuntu软件包系统安装我的软件包,该脚本还将删除两个目录。

这是对的还是我缺少什么?

Answers

如果命令或管道有错误-与默认的shell行为相反(即忽略脚本中的错误),则set -e停止执行脚本。在终端中键入help set以查看此内置命令的文档。

help set

  -e  Exit immediately if a command exits with a non-zero status.

但这被某些人(bash FAQ和irc freenode #bash FAQ的作者)认为是不好的做法。建议使用:

trap 'do_something' ERR

发生错误时运行do_something函数。

参见http://mywiki.wooledge.org/BashFAQ/105

按照bash-Set Builtin手册,如果设置了 -e / errexit ,则当由单个简单命令列表复合命令组成的管道返回非零状态时,shell将立即退出。

默认情况下,管道的退出状态是管道中最后一个命令的退出状态,除非启用了pipefail选项(默认情况下pipefail禁用状态)。

如果是这样,则管道的最后(最右边)命令的返回状态以非零状态退出;如果所有命令成功退出,则返回零。

如果您想在退出时执行某些操作,请尝试定义trap ,例如:

trap onexit EXIT

其中onexit是您在退出时执行某项操作的函数,例如下面的代码显示了简单的堆栈跟踪

onexit(){ while caller $((n++)); do :; done; }

有一个类似的选项-E / errtrace会捕获在ERR上,例如:

trap onerr ERR

例子

零状态示例:

$ true; echo $?
0

非零状态示例:

$ false; echo $?
1

否定状态示例:

$ ! false; echo $?
0
$ false || true; echo $?
0

在禁用pipefail进行测试:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

在启用pipefail进行测试:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1

我在尝试找出由于set -e而中止的脚本的退出状态是什么时找到了这篇文章。答案对我来说似乎并不明显。因此这个答案。基本上, set -e中止命令(例如,shell脚本)的执行,并返回失败命令的退出状态代码(即内部脚本,而不是外部脚本)

例如,假设我有外壳程序脚本outer-test.sh

#!/bin/sh
set -e
./inner-test.sh
exit 62;

inner-test.sh的代码是:

#!/bin/sh
exit 26;

当我从命令行运行outer-script.sh ,我的外部脚本以内部脚本的退出代码终止:

$ ./outer-test.sh
$ echo $?
26

这是一个古老的问题,但是这里没有答案讨论在Debian软件包处理脚本中使用set -e aka set -o errexit问题。根据Debian策略,在这些脚本中必须使用此选项。显然是为了避免发生未处理的错误情况。

实际上,这意味着您必须了解在什么条件下运行的命令可能会返回错误,并明确处理每个错误。

常见的陷阱是例如diffdiff返回错误)和grep (没有匹配项时返回错误)。您可以通过显式处理避免错误:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(还请注意,我们如何注意在消息中包含当前脚本的名称,以及如何将诊断消息写入标准错误而不是标准输出。)

如果没有显式处理确实必要或无用,则显式不执行任何操作:

diff this that || true
grep cat food || :

(使用shell的: no-op命令有点晦涩,但是很常见。)

只是重申一下,

something || other

是的简写

if something; then
    : nothing
else
    other
fi

也就是说,我们明确表示只有当something失败时,才应运行other 。常用的if (以及其他shell流程控制语句,如whileuntil )也是处理错误的有效方法(实际上,如果不是,则带有set -e shell脚本永远不会包含流程控制语句!)

而且,为了明确起见,如果没有这样的处理程序,则在diff发现差异或grep找不到匹配项的情况下, set -e将导致整个脚本立即因错误而失败。

另一方面,某些命令在您需要时不会产生错误退出状态。通常会find有问题的命令(退出状态并不反映是否实际找到了文件)并被sed (退出状态不会显示脚本是否接收到任何输入或实际上是否成功执行了任何命令)。在某些情况下,一种简单的防护措施是通过管道传递给没有输出时发出尖叫的命令:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

应该注意的是,管道的退出状态是该管道中最后一条命令的退出状态。因此,以上命令实际上完全掩盖了findsed的状态,仅告诉您grep是否最终成功。

(当然,Bash set -o pipefail ;但是Debian软件包脚本不能使用Bash功能。该策略set -o pipefail规定了对这些脚本使用POSIX sh ,尽管并非总是如此。)

在许多情况下,进行防御性编码时需要格外注意。有时,您必须例如浏览一个临时文件,以便您可以查看产生该输出的命令是否成功完成,即使习惯用语和便利性会导致您直接使用Shell管道也是如此。

我相信这样做的目的是使该脚本快速失败。

要自己进行测试,只需在bash提示符下键入set -e 。现在,尝试运行ls 。您会得到一个目录清单。现在,键入lsd 。该命令无法识别,并且将返回错误代码,因此您的bash提示符将关闭(由于set -e )。

现在,要在“脚本”的上下文中理解这一点,请使用以下简单脚本:

#!/bin/bash 
# set -e

lsd 

ls

如果按原样运行,则会在最后一行的ls获得目录列表。如果取消注释set -e并再次运行,则不会看到目录列表,因为一旦bash遇到来自lsd的错误,它将停止处理。

Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further

Related