949

我正在研究该脚本在从其 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 is just installed”并结束(return 1意味着以“错误”结束,不是吗?)。

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

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

4

9 回答 9

1006

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

于 2013-10-27T19:39:10.623 回答
163

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

于 2013-10-27T19:14:29.360 回答
88

我在试图弄清楚由于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
于 2016-08-05T22:54:34.417 回答
67

根据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
于 2015-12-20T13:31:37.337 回答
22

这是一个老问题,但这里的答案都没有讨论set -eakaset -o errexit在 Debian 包处理脚本中的使用。根据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(和其他 shell 流控制语句,如while, until)也是处理错误的有效方法(事实上,如果不是,shell 脚本set -e永远不会包含流控制语句!)

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

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

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

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

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

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

于 2018-12-04T12:04:24.193 回答
20

我相信这样做的目的是让有问题的脚本快速失败。

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

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

#!/bin/bash 
# set -e

lsd 

ls

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

于 2018-12-14T21:55:21.060 回答
12
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
于 2019-05-14T06:46:20.400 回答
8

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

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

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

这可能会帮助你。

于 2021-09-27T05:07:49.900 回答
1
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 选项。

于 2020-10-11T06:30:18.287 回答