bash 脚本中的 set -e 是啥意思?

Posted

技术标签:

【中文标题】bash 脚本中的 set -e 是啥意思?【英文标题】:What does set -e mean in a bash script?bash 脚本中的 set -e 是什么意思? 【发布时间】:2013-11-06 11:33:38 【问题描述】:

我正在研究这个 preinst 文件的内容,该文件在该软件包从其 Debian 归档 (.deb) 文件中解压缩之前执行。

脚本代码如下:

#!/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 is just installed” 并结束(return 1 表示以“错误”结束,不是吗?)。

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

这是对的还是我错过了什么?

【问题讨论】:

set -e 在 google 中找不到此内容的原因: -e 在您的查询中被解释为否定。尝试以下查询:bash set "-e" @twalberg 当我问自己同样的问题时,我正在查看man set 如果您正在寻找如何关闭它,请将破折号换成加号前缀:set +e @twalberg 但是询问真人比仅仅向机器人发出请求要有趣得多;-)。 【参考方案1】:

来自help set

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

但某些人(bash 常见问题解答和 irc freenode #bash 常见问题解答作者)认为这是不好的做法。推荐使用:

trap 'do_something' ERR

发生错误时运行do_something函数。

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

【讨论】:

如果我想要与“如果命令以非零状态退出则立即退出”相同的语义,do_something 会是什么? ERR 陷阱不被 shell 函数继承,所以如果你有函数,set -o errtraceset -E 将允许你只设置一次陷阱并全局应用它。 trap 'exit' ERRset -e 有什么不同的 如果这是不好的做法,那么为什么要在 Debian packages 中使用它? 不是普遍认为是不好的做法。与许多不受欢迎的语言结构一样,它也有它的位置。它的主要问题是边缘情况下的行为有点不直观。【参考方案2】:

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

【讨论】:

只有在管道中的 last 命令出现错误时才会停止执行。有一个 Bash 特定选项 set -o pipefail 可用于传播错误,因此如果前面的命令之一以非零状态退出,则管道命令的返回值非零。 请记住,-o pipefail 仅表示管道的第一个非零(即 -o errexit 术语中的错误)命令的 退出状态 被传播到结束。管道中的其余命令仍在运行,即使使用set -o errexit。例如:echo success | cat - <(echo piping); echo continues,其中echo success 表示成功但可能出错的命令,将打印successpipingcontinues,但false | cat - <(echo piping); echo continuesfalse 表示命令现在静默错误,退出前仍会打印piping【参考方案3】:

我在试图弄清楚由于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

【讨论】:

【参考方案4】:

根据bash - The Set Builtin 手册,如果设置了-e/errexit,如果由单个simple command、a list 或a compound command 组成的pipeline 返回非零值,则shell 将立即退出状态。

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

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

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

trap onexit EXIT

onexit 是您在退出时执行某些操作的函数,如下所示,它打印简单的 stack trace:

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

【讨论】:

【参考方案5】:

这是一个老问题,但这里的答案都没有讨论在 Debian 包处理脚本中使用 set -e aka 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

即当且仅当something 失败时,我们明确地说应该运行other。普通的if(以及其他shell 流控制语句,如whileuntil)也是处理错误的有效方法(事实上,如果不是这样,带有set -e 的shell 脚本永远不会包含流控制声明!)

另外,明确地说,在没有这样的处理程序的情况下,set -e 将导致整个脚本立即失败并出现错误,如果diff 发现差异,或者如果grep 没有找到匹配项。

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

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

需要注意的是,管道的退出状态是该管道中最后一个命令的退出状态。所以上面的命令实际上完全掩盖了findsed的状态,只告诉你grep最后是否成功了。

(当然,Bash 有 set -o pipefail;但 Debian 软件包脚本不能使用 Bash 功能。政策坚决要求这些脚本使用 POSIX sh,尽管并非总是如此。)

在许多情况下,在进行防御性编码时需要单独注意这一点。有时你必须例如浏览一个临时文件,这样您就可以看到产生该输出的命令是否成功完成,即使习惯用法和方便会指示您使用 shell 管道。

【讨论】:

这是一个很好的答案。它促进了最佳实践。我在 GREP 命令中遇到了完全相同的问题,我真的不想删除 'set -e'【参考方案6】:

我认为其目的是让相关脚本快速失败。

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

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

#!/bin/bash 
# set -e

lsd 

ls

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

【讨论】:

此答案是否添加了其他人尚未就该问题提供的任何见解或信息? 我认为它对其他答案中不存在的功能提供了清晰、简洁的解释。没有什么额外的,只是比其他回复更专注。 @CharlesDuffy 我认为确实如此。这比仅仅说“查看手册页”有用得多 另一个答案只是说“查看手册页”——它会提取手册页中相关且重要的特定部分。正是由于没有具体说明(以及要求读者自己进行研究),才使得“查看手册页”没有帮助。 我也认为这个答案很有帮助@CharlesDuffy【参考方案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

【讨论】:

【参考方案8】:

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

默认情况下,bash 不这样做。如果您在命令行上使用 bash,则此默认行为正是您想要的 您不想因为输入错误而退出!但在脚本中,您确实想要相反的结果。 如果脚本中的一行失败,但最后一行成功,则整个脚本都有一个成功的退出代码。这样很容易漏掉错误。 同样,当您使用 bash 作为命令行 shell 并在脚本中使用它时,您想要的结果在这里不一致。在脚本中不容忍错误要好得多,这就是 set -e 给你的。

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

这可能会对你有所帮助。

【讨论】:

“如果你在命令行上使用 bash,这个默认行为正是你想要的”——更清楚一点:如果你在运行 bash 命令时使用set -e,一个简单的错字可能会导致你bash 会话立即退出。尝试运行一个新的终端,set -e,然后是lsd。再见,终点站。【参考方案9】:
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 directory
hi
0

使用 set -e 注释掉,我们看到 echo "hi" 退出状态被报告并且 hi 被打印出来。

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 directory
1

现在我们看到报告了 b.txt 错误,并且没有打印任何 hi。

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

【讨论】:

以上是关于bash 脚本中的 set -e 是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章

linux的shell编程中#!/bin/sh和bash是啥意思?

bash 中的 $# 结构是啥意思? [复制]

shell脚本中#是啥意思

linux的shell编程中#!/bin/sh和$bash是啥意思?

linux 命令中的sh是啥意思??

脚本中的#! /bin/bash是什么意思?