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


713

我正在研究脚本从该脚本的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软件包系统安装我的软件包,该脚本还将删除两个目录。

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



46
为什么您无法在google中找到此原因:-e在您的查询中被解释为否定。尝试以下查询:bash设置为“ -e”
Maleev

3
@twalberg当我问自己同样的问题时,我在看man set
Sedat Kilinc

4
如果您要关闭它,请将破折号换成加号:set +e
Tom Saleeba 18-3-13

@twalberg,但是向真实的人问问比从机器人发出请求要有趣得多;-)。
vdegenne

Answers:


797

来自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


14
如果我想要与“如果命令以非零状态退出则立即退出”相同的语义,那么do_something是什么?
CMCDragonkai 2014年

71
trap 'exit' ERR
chepner 2014年

12
ERR陷阱不是由shell函数继承的,所以如果你有功能, set -o errtraceset -E将允许你刚才设置的陷阱,一旦和全局应用。
ykay

31
没有trap 'exit' ERR任何事情,从不同set -e
安迪

22
如果是不好的做法,那么为什么在Debian软件包中使用它呢?
phuclv

98

set -e如果命令或管道有错误,则停止执行脚本-这与默认的shell行为相反,后者将忽略脚本中的错误。键入help set在终端看到的文档,这个内置的命令。


44
仅当管道中的最后一条命令出错时,它才会停止执行。有一个特定于Bash的选项,set -o pipefail该选项可用于传播错误,以便如果前面的命令之一以非零状态退出,则管道命令的返回值将为非零。
Anthony Geoghegan 2015年

2
请记住,-o pipefail这仅意味着流水线的第一个非零(即术语错误)命令的退出状态-o errexit会传播到末尾。即使使用,管道中的其余命令仍会运行set -o errexit。例如:echo success | cat - <(echo piping); echo continues,其中echo success代表成功但容易出错的命令,将打印successpipingcontinues,但是false | cat - <(echo piping); echo continues,与false代表命令现在默默示数,仍然会打印piping飞去。
bb010g

54

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

默认情况下,管道的退出状态是管道中最后一个命令的退出状态,除非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

54

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

例如,假设我有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

10

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

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

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

#!/bin/bash 
# set -e

lsd 

ls

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


此答案是否添加了其他人在该问题上尚未提供的任何见解或信息?
查尔斯·达菲

6
我认为它提供了其他答案中未提供的功能的清晰明了的解释。没有什么比其他回应更集中的了。
Kallin Nagelberg '18

9

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

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

常见的陷阱有(例如,diff当有差异时返回错误)和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

即,我们明确表示other只有当something失败时才应运行。该手写if(等外壳流控制之类的语句whileuntil)也是处理错误(的确是一个有效的方式,如果不是,shell脚本与set -e可能永远不会包含流控制语句!)

而且,明确地说,如果没有这样的处理程序,set -e如果diff发现差异或grep找不到匹配项,则会导致整个脚本立即因错误而失败。

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

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

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

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

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


这是一个很好的答案。它促进了最佳实践。我从grep命令有exactally同样的问题,我真的不想删除“集合-e”
米妮施

7
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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.