繁体   English   中英

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

[英]What does set -e mean in a bash script?

我正在研究该脚本在 package 从其 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

我认为脚本的 rest 非常简单:它检查 Debian/Ubuntu package 管理器是否正在执行安装操作。 如果是,它会检查我的应用程序是否刚刚安装在系统上。 如果有,脚本会打印消息“MyApplicationName is just installed”并结束( return 1表示以“错误”结束,不是吗?)。

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

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

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

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

我在试图找出由于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

根据bash - The 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

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

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

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

#!/bin/bash 
# set -e

lsd 

ls

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

这是一个老问题,但这里的答案都没有讨论在 Debian 包处理脚本中使用set -e aka set -o errexit 根据 Debian 政策,在这些脚本中必须使用此选项; 目的显然是为了避免任何未处理错误情况的可能性。

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

常见的问题是例如diffdiff返回错误)和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 功能。该政策set -o pipefail规定这些脚本使用 POSIX sh ,尽管情况并非总是如此。)

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

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

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

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

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

这可能对你有帮助。

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

一个值得注意的例外是if语句。 例如:

set -e
false
echo never executed
set -e
if false; then
  echo never executed
fi

echo executed

false

echo never executed

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM