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

我正在研究脚本在从其Debian存档(. deb)文件中解包之前执行的这个preinst文件的内容。

该脚本具有以下代码:

#!/bin/bashset -e# Automatically added by dh_installinitif [ "$1" = install ]; thenif [ -d /usr/share/MyApplicationName ]; thenecho "MyApplicationName is just installed"return 1firm -Rf $HOME/.config/nautilus-actions/nautilus-actions.confrm -Rf $HOME/.local/share/file-manager/actions/*fi# End automatically added section

我的第一个问题是关于该行:

set -e

我认为脚本的其余部分非常简单:它检查Debian/Ubuntu软件包管理器是否正在执行安装操作。如果是,它检查我的应用程序是否刚刚安装在系统上。如果是,脚本打印消息"MyApplication ationName刚刚安装"并结束(return 1意味着以“错误”结束,不是吗?)。

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

这是正确的还是我错过了什么?

598472 次浏览

如果命令或管道出现错误,set -e将停止脚本的执行-这与默认shell行为相反,即忽略脚本中的错误。在终端中键入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-套装内置手册,如果设置了-e/errexit,如果由单个简单的命令一个列表一个复合命令组成的管道返回非零状态,shell会立即退出。

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

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

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

trap onexit EXIT

其中onexit是您在退出时执行某些操作的函数,如下所示,它打印简单的堆栈跟踪

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

有类似的选项-E,它会陷入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 $?success0$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?success0$ 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

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

#!/bin/shset -e./inner-test.shexit 62;

inner-test.sh的代码是:

#!/bin/shexit 26;

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

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

这是一个老问题,但这里的答案都没有讨论在Debian包处理脚本中使用set -e也就是set -o errexit。根据Debian政策,在这些脚本中使用此选项是强制性;其目的显然是为了避免任何未处理错误情况的可能性。

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

常见的陷阱是diff(当存在差异时返回错误)和grep(当没有匹配时返回错误)。您可以通过显式处理来避免错误:

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

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

如果没有显式处理是真正必要或有用的,则显式不做任何事情:

diff this that || truegrep cat food || :

(shell的: no-op命令的使用有点模糊,但相当常见。)

只是重申一下,

something || other

if something; then: nothingelseotherfi

即我们明确地说other应该在something失败时运行。长号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功能。该策略明确规定这些脚本使用POSIXsh,尽管情况并非总是如此。)

在许多情况下,这是防御编码时需要单独注意的事情。有时你必须例如浏览一个临时文件,这样你就可以看到产生该输出的命令是否成功完成,即使习语和便利会指导你使用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/bashdecho "hi"echo "hello"This will throw error in decho and program continuous to next line
Script 2: With setting -e#!/bin/bashset -edecho "hi"echo "hello"# Up to decho "hi" shell will process and program exit, it will not proceed further
cat a.sh#! /bin/bash
#going forward report subshell or command exit value if errors#set -e(cat b.txt)echo "hi"
./a.sh; echo $?cat: b.txt: No such file or directoryhi0

使用set-e注释,我们看到回显“hi”退出状态被报告并且hi被打印。

cat a.sh#! /bin/bash
#going forward report subshell or command exit value if errorsset -e(cat b.txt)echo "hi"
./a.sh; echo $?cat: b.txt: No such file or directory1

现在我们看到b.txt错误被报告,而不是打印。

所以外壳脚本的默认行为是忽略命令错误并继续处理并报告最后一个命令的退出状态。如果您想在错误时退出并报告其状态,我们可以使用-e选项。

set-eset-e选项指示bash在任何命令[1]具有非零退出状态时立即退出。您不希望为命令行shell设置此项,但在脚本中它非常有用。在所有广泛使用的通用编程语言中,未处理的运行时错误-无论是Java中抛出的异常,还是C中的分段错误,还是Python中的语法错误-都会立即停止程序的执行;后续行不会被执行。

  • 默认情况下,bash不会这样做。如果您在命令行上使用bash,此默认行为正是您想要的
  • 你不希望一个错别字让你注销!但是在脚本中,你真的想要相反的东西。
  • 如果脚本中的一行失败,但最后一行成功,则整个脚本都有一个成功的退出代码。这很容易错过错误。
  • 同样,在使用bash作为命令行shell和在脚本中使用它时,您想要的是不一致的。在脚本中不能容忍错误要好得多,这就是set-e给您的。

复制自:https://gist.github.com/mohanpedala/1e2ff5661761d3abd0385e8223e16425

这可能对你有帮助。

如果命令失败,它会停止执行脚本。

一个明显的例外是if声明。例如:

set -efalseecho never executed
set -eif false; thenecho never executedfi
echo executed
false
echo never executed