set -e在bash脚本中的含义是什么?

And*_*ili 617 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
Run Code Online (Sandbox Code Playgroud)

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

set -e
Run Code Online (Sandbox Code Playgroud)

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

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

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

Gil*_*not 703

来自help set:

  -e  Exit immediately if a command exits with a non-zero status.
Run Code Online (Sandbox Code Playgroud)

但有些人认为这是不好的做法(bash常见问题和人们在irc freenode #bash上撰写此常见问题解答)说它更好用:

trap 'do_something' ERR
Run Code Online (Sandbox Code Playgroud)

do_something发生错误时运行功能.

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

  • `陷阱'退出'ERR` (64认同)
  • '陷阱'退出'ERR`做什么*任何*与`set -e`不同? (28认同)
  • 如果这是不好的做法那么为什么它在[Debian软件包]中使用(http://unix.stackexchange.com/q/325705/44425)? (17认同)
  • 如果我想要"如果命令以非零状态退出则立即退出"这样的语义,do_something会是什么? (13认同)
  • `ERR`陷阱不是由shell函数继承的,所以如果你有函数,`set -o errtrace`或`set -E`将允许你只设置一次陷阱并全局应用它. (12认同)
  • 这不是普遍认为不好的做法.与许多不受欢迎的语言结构一样,它有其自己的位置.它的主要问题是边缘情况下的行为有些不直观. (8认同)
  • 我也不同意这被认为是不好的做法.您提供的链接提到它不是一个完整的解决方案,并且存在问题,就像任何类型的编程解决方案一样.现在是时候使用它和错误的时间来使用它.正如您所链接的文章中的rking状态所说:"它具有有用的语义,因此将其从工具箱中排除是为FUD提供的." (7认同)
  • ...就他们不同意的人而言,他们是有很多时间/经验的人试图为现实世界中的bash用户提供支持(从而了解新用户在何处以及如何被绊倒),这种经验可以在多大程度上告知他们关于最好避免使用哪种语言功能的信念,这可能值得关注。同样,我会信任一个花了多年的时间来帮助调试n00bs编写的JavaScript到该语言的人,以了解哪些语言功能容易出错并且最好避免使用某些随机JS开发人员。 (3认同)
  • @GuyPaddock,...用户名对那些没有在freenode #bash频道上度过时间的人来说意义不大,但是争论的“ set -e不应该被使用”的人是已知的实体- -greycat首先写了BashFAQ;geirha编写了许多工具,包括用于在隔离的VM中测试命令的“ evalbot”沙箱。当然,这是权威人士的争论,但是作为一个在那里呆了十年的人,我不知道这个“ RKing”人是谁,但是我肯定*知道*他们不同意的人。 (2认同)

Rob*_*een 91

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

  • 如果管道中的**last**命令有错误,它只会停止执行.有一个Bash特定选项,`set -o pipefail`,可用于传播错误,以便如果上述命令之一退出非零状态,则管道命令的返回值不为零. (43认同)
  • 请记住,“-o pipelinefail”仅意味着管道的第一个非零(即“-o errexit”术语中的错误)命令的“退出状态”传播到末尾。即使使用“set -o errexit”,管道中的其余命令*仍然运行*。例如:`回显成功| cat - <(回声管道);echo continue`,其中`echo success`表示成功但可能出错的命令,将打印`success`、`piping`和`continues`,但`false | cat - <(回声管道);echo continue`,其中`false`代表命令现在默默地出错,在退出之前仍会打印`piping`。 (4认同)

ken*_*orb 49

根据bash - Set Builtin手册,如果设置了-e/ errexit,如果管道由单个简单命令,列表复合命令返回非零状态,则shell立即退出.

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

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

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

trap onexit EXIT
Run Code Online (Sandbox Code Playgroud)

这里onexit是你的函数做一些事情上退出,像下面这是印刷简单的堆栈跟踪:

onexit(){ while caller $((n++)); do :; done; }
Run Code Online (Sandbox Code Playgroud)

有类似的选项-E/errtrace会在ERR上陷阱,例如:

trap onerr ERR
Run Code Online (Sandbox Code Playgroud)

例子

零状态示例:

$ true; echo $?
0
Run Code Online (Sandbox Code Playgroud)

非零状态示例:

$ false; echo $?
1
Run Code Online (Sandbox Code Playgroud)

否定状态示例:

$ ! false; echo $?
0
$ false || true; echo $?
0
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

pipefail启用测试:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
Run Code Online (Sandbox Code Playgroud)


ent*_*erd 47

我在谷歌搜索时发现了这个问题,试图找出由于a而被中止的脚本的退出状态set -e.答案对我来说并不明显; 因此这个答案.基本上,set -e中止命令的执行(例如shell脚本)并返回失败命令的退出状态代码(即内部脚本,而不是外部脚本).

例如,假设我有一个shell脚本outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;
Run Code Online (Sandbox Code Playgroud)

代码inner-test.sh是:

#!/bin/sh
exit 26;
Run Code Online (Sandbox Code Playgroud)

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

$ ./outer-test.sh
$ echo $?
26
Run Code Online (Sandbox Code Playgroud)


小智 29

set -e set -e选项指示 Bash 在任何命令1 具有非零退出状态时立即退出。您不想为命令行 shell 设置此选项,但在脚本中它非常有用。

在所有广泛使用的通用编程语言中,未处理的运行时错误(无论是Java中抛出的异常、C中的分段错误还是Python中的语法错误)都会立即停止程序的执行;后续行不被执行。

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

复制自:Bash 严格模式

  • “如果您在命令行上使用 bash,这种默认行为正是您想要的”——更清楚地说:如果您在运行 bash 命令时使用 `set -e`,一个简单的拼写错误可能会导致您的 bash 会话立即退出。尝试运行一个新终端,“set -e”,然后“lsd”。再见,去航站楼。 (2认同)

Kal*_*erg 8

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

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

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

#!/bin/bash 
# set -e

lsd 

ls
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为它提供了其他答案中没有的功能的清晰,简洁的解释。没有什么比其他回应更集中的了。 (5认同)

tri*_*eee 7

这是一个老问题,但是这里没有答案讨论在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
Run Code Online (Sandbox Code Playgroud)

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

如果没有显式处理是真正必要或有用的,则显式不执行任何操作:

diff this that || true
grep cat food || :
Run Code Online (Sandbox Code Playgroud)

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

只是重申一下,

something || other
Run Code Online (Sandbox Code Playgroud)

是的简写

if something; then
    : nothing
else
    other
fi
Run Code Online (Sandbox Code Playgroud)

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

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

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

find things | grep .
sed -e 's/o/me/' stuff | grep ^
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 这是一个很好的答案。它促进了最佳实践。我在 GREP 命令中遇到了完全相同的问题,我真的不想删除“set -e” (2认同)

小智 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
Run Code Online (Sandbox Code Playgroud)


mxc*_*xcl 6

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

一个值得注意的例外是if声明。例如:

set -e
false
echo never executed
Run Code Online (Sandbox Code Playgroud)
set -e
if false; then
  echo never executed
fi

echo executed

false

echo never executed
Run Code Online (Sandbox Code Playgroud)