ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

J
Jakov

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


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

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 continues,其中 false 表示命令现在静默出错,退出前仍会打印 piping
R
Reactive

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

C
Community

根据 bash - The Set Builtin 手册,如果设置了 -e/errexit,如果由单个 simple commanda lista 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

t
tripleee

这是一个老问题,但这里的答案都没有讨论在 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 脚本永远不会包含流控制声明!)

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

另一方面,某些命令不会在您希望的时候产生错误退出状态。常见的有问题的命令是 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”
K
Kallin Nagelberg

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

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

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

#!/bin/bash 
# set -e

lsd 

ls

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


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

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。再见,终点站。
M
Manikandan Raj
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

M
Miten
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 选项。