Bash 脚本中的“set -e”是什么意思?

2024-09-30 14:02:00
admin
原创
135
摘要:问题描述:我正在研究该文件的内容preinst,该文件是在该包从其Debian档案 ( .deb) 文件中解压之前执行的脚本。该脚本包含以下代码:#!/bin/bash set -e # Automatically added by dh_installinit if [ "$1&q...

问题描述:

我正在研究该文件的内容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 刚刚安装”并结束(return 1这意味着脚本以错误退出,不是吗?)。

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

这是正确的吗或者我遗漏了什么?


解决方案 1:

Fromhelp setBash 参考文档:内置 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发生错误时运行函数。

请参阅为什么 set -e(或 set -o errexit 或 trap ERR)没有按照我的预期执行?

解决方案 2:

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

解决方案 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 - 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

解决方案 5:

这里的其他答案都没有讨论在 Debian 包处理脚本中使用set -eaka的问题。根据 Debian 政策set -o errexit,强烈建议在这些脚本中使用此选项;目的显然是为了避免出现任何未处理的错误情况。

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

常见的陷阱有,例如,diff(当存在差异时返回错误)和grep(当不匹配时返回错误)。您可以通过显式处理来避免错误:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

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

也许还注意到算术评估可以产生这种效果。

((variable++))

variable如果为零则会失败。

如果确实不需要或没有有用的显式处理,则明确不执行任何操作:

diff this that || true
grep cat food || :

(shell 的 no-op 命令的用法:有点晦涩,但相当常见。)

再次重申,

something || other

是简写

if something; then
    : nothing
else
    other
fi

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

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

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

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

find需要注意的是,管道的退出状态是该管道中最后一个命令的退出状态。因此上述命令实际上完全屏蔽了和 的状态sed,只告诉你grep最终是否成功。

(Bash 当然有set -o pipefail,如果管道中的任何命令失败,它就会抛出错误;但是 Debian 包脚本不能使用 Bash 功能。该政策明确规定这些脚本必须使用 POSIX sh,尽管情况并非总是如此。)

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

解决方案 6:

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

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

  • 默认情况下,Bash 不会这样做。如果您在命令行上使用 Bash,此默认行为正是您想要的

  • 您不希望因为输入错误而导致退出!但在脚本中,您真正想要的是相反的结果。

  • 如果脚本中有一行失败,但最后一行成功,则整个脚本的退出代码为成功。这使得很容易忽略错误。

  • 再次强调,使用 Bash 作为命令行 shell 和在脚本中使用它时,您的需求是不同的。在脚本中,对错误的容忍度要高得多,这就是set -e您所得到的。

复制自:Bash 严格模式

解决方案 7:

我相信其目的是让所讨论的脚本快速失败。

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

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

#!/bin/bash
# set -e

lsd

ls

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

解决方案 8:

脚本1:无需设置-e

#!/bin/bash
decho "hi"
echo "hello"

这将在该行引发错误decho,并且程序继续执行下一行。

脚本 2:使用设置-e

#!/bin/bash
set -e
decho "hi"
echo "hello"

直到decho "hi",shell 才会处理,然后程序退出。它不会再继续执行。

解决方案 9:

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

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

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

echo executed

false

echo never executed

解决方案 10:

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

解决方案 11:

set -e意味着脚本的读者在脱离上下文查看代码片段时无法知道脚本将做什么。 (这有点讽刺,但却切中要害。)errexit多年来我一直反对使用,最近在将 shell 片段积累为模板的环境中做了大量工作。 errexit在此环境中默认启用,但这在很大程度上无关紧要,因为任何先前的代码片段都可能禁用 errexit,并且收集模板的顺序并不为人所知,并且可能是不确定的。但请考虑一个简单的情况,您在没有上下文的情况下查看 shell 脚本中的几行:

$ sed -n 3,6p sample-script 
echo this will be printed
set -e  # enable errexit again just to be sure it's on
false
echo but errexit is enabled so this will not be printed

由于 errexit 已启用,因此读者可以合理地预期“但 errexit 已启用,因此不会打印”这一行实际上不会出现。但是,请考虑脚本的实际执行情况:

$ ./sample-script 
this will be printed
but errexit is enabled so this will not be printed

没有任何神奇的魔力,也没有任何非常奇怪的事情导致这种行为。这只是 shell 的正常行为。另一方面,如果我们稍微更改脚本并明确所需的行为,意外行为就会消失。考虑一下:

$ cat sample-script-2 
#!/bin/bash -e
if
echo this will be printed
false || exit
echo but this is not printed because the author was explicit about exiting
then : ; fi
$ ./sample-script-2 
this will be printed

errexit 是为了让脚本编写者和维护者的工作更轻松的一种尝试,但它失败了。而且它还在继续失败。借用 python 的话:“显式优于隐式”。如果您希望脚本在命令返回非零时终止,请写入cmd || exit。如果您希望脚本在命令返回非零时有时失败,并且您希望确保脚本的维护者真的不知道实际发生了什么,请写入set -e; cmd

解决方案 12:

我迟迟才发布有关问题中特定代码的单独答案。它有多个问题。

# Automatically added by dh_installinit

这有点误导,因为脚本的作者显然修改了实际上自动添加的代码。

if [ "$1" = install ]; then

preinst脚本将在多种情况下运行,并接收一个参数,该参数指示哪种情况导致它被调用。如果操作是install(而不是,例如,upgradeabort-upgrade,这是另外两个可能的值;请参阅https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html),则此部分将运行

   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi

这似乎很可疑。该echo命令可能不会打印到任何有用的地方,并return 1导致整个安装失败。

更切题的是,MyApplicationName有异常的大写字母;典型的值更像my_application是或my-application

   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*

这些也看起来非常可疑。包管理系统不应该触碰任何用户的任何非系统文件。此外,当此脚本运行时,$HOME可能会发生这种情况/root,因此作者希望在这里实现的任何目标可能实际上都不会发生。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   681  
  在项目管理领域,集成产品开发(IPD)流程以其高效、协同的特点,被众多企业视为提升产品竞争力的关键。IPD流程强调跨部门、跨职能的紧密合作,以确保产品从概念到市场各个环节的无缝衔接。然而,实现这一目标并非易事,它需要企业深刻理解并掌握IPD流程中的跨部门协作艺术。本文将深入探讨IPD流程中跨部门协作的三个关键点,旨在为...
IPD项目管理咨询   9  
  掌握IPD流程图:提升团队协作的关键路径在当今快速变化的商业环境中,团队协作的效率与效果直接关系到项目的成功与否。集成产品开发(Integrated Product Development,简称IPD)作为一种先进的研发管理理念,通过跨部门、跨领域的协同工作,能够显著提升产品开发的速度与质量。而IPD流程图,则是这一理...
IPD流程阶段   9  
  IPD流程概述:理解其核心价值与实施背景集成产品开发(Integrated Product Development,简称IPD)是一种先进的产品开发管理理念,它强调跨部门协作、市场导向和快速响应变化的能力。IPD流程不仅关注产品本身的技术创新,更注重将市场、研发、生产、销售等各个环节紧密集成,以实现产品从概念到市场的高...
华为IPD是什么   7  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程以其跨部门协作、高效决策和快速响应市场变化的特点,被众多企业视为提升竞争力的关键。然而,实践IPD流程并非易事,项目管理中的种种错误往往阻碍了其效果的充分发挥。本文旨在深入探讨如何在实施IPD流程时避免这些常见错误,...
IPD框架   7  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用