“ set -e”有什么作用,为什么会被认为是危险的?


Answers:


154

set -e 如果任何子命令或管道返回非零状态,将导致外壳退出。

面试官可能正在寻找的答案是:

创建init.d脚本时使用“ set -e”是危险的:

http://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2-

注意在init.d脚本中使用set -e。编写正确的init.d脚本要求在守护程序已经运行或已经停止时接受各种错误退出状态,而不会中止init.d脚本,并且使用set -e进行调用时,通常的init.d函数库并不安全。对于init.d脚本,不使用set -e而不是分别检查每个命令的结果通常会更容易。

从面试官的角度来看,这是一个有效的问题,因为它可以评估应聘者在服务器级脚本和自动化方面的工作知识


好点子。它将停止启动过程过的东西,可能是技术上的错误,但应该不会导致整个脚本停止
马特

尽管我同意stackoverflow.com/questions/78497/…,但我认为这种样式非常适合bash用于短期init检查和日志记录或其他环境猴子修补,然后再执行目标工作负载。我们对docker镜像使用相同的策略初始化脚本。
joshperry

33

来自bash(1)

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see SHELL GRAMMAR
                  above) exits with a non-zero status.  The shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the shell exits.  This option
                  applies to the shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

不幸的是,除了“脚本的其余部分将无法执行”或“它可能掩盖实际问题”之外,我没有足够的创意去思考为什么这样做会很危险。


1
掩盖真实/其他问题,是的,我想我可以看到...我也在努力举一个例子说明它也很危险...
cpbills 2010年

有手册set!我总是以builtin(1)。谢谢。
杰里德·贝克

我怎么知道该文档set将在man 1 bash?? 中找到?以及如何在巨大的手册页中找到该关键字的-e选项set?您不能只/-e在这里搜索
Blauhirn

@Blauhirn使用help set
Blauhirn

19

应当注意,set -e可以为脚本的各个部分打开和关闭。整个脚本的执行不必一定要启用。甚至可以有条件地启用它。就是说,我从来没有使用过它,因为我自己进行错误处理(或不进行错误处理)。

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

同样值得注意的是该trap命令的Bash手册页部分,该部分还描述了set -e在某些情况下的功能。

如果失败的命令是紧随while或直到关键字之后的命令列表的一部分,if语句中的测试的一部分,在&&或⎪⎪列表中执行的命令的一部分,或者命令的命令的一部分,则不执行ERR陷阱。返回值通过!反转。这些是errexit选项遵循的相同条件。

因此,在某些情况下,非零状态不会导致退出。

我认为危险在于不了解何时set -e发挥作用以及何时不发挥作用,并且在某些无效的假设下错误地依赖它。

另请参见BashFAQ / 105。为什么不设置-e(或设置-o errexit或陷阱ERR),却没有达到我的期望?


尽管稍微偏离了这个问题,但这个答案正是我要寻找的:只在脚本的一小部分将其关闭!
斯蒂芬·比耶吉特

13

请记住,这是求职面试的一项测验。这些问题可能是当前员工写的,可能是错误的。这并不一定是坏事,每个人都会犯错,面试问题通常位于一个黑暗的角落,没有经过审查,只有在面试时才会出现。

“ set -e”完全没有做任何我们认为“危险”的事情。但是,该问题的作者可能会误认为“ set -e”是危险的,因为他们自己的无知或偏见。也许他们编写了一个有问题的shell脚本,遭到了轰炸,然后他们错误地认为是“ set -e”的罪魁祸首,而实际上他们却忽略了编写适当的错误检查程序。

在过去的两年中,我大概参加了40次面试,而面试官有时会提出错误的问题,或者给出错误的答案。

也许这是一个棘手的问题,这很la脚,但并非完全出乎意料。

也许这是一个很好的解释:http : //www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg473314.html


1
+1我在同一条船上,而我一直在进行的许多“技术”面试至少被误导了。
cpbills 2010年

1
哇。在debian列表上很好找到。+1是对面试问题的无知。我记得有一次因为我100%确信自己是对的而争论过一次netback答案。他们说不。我回家搜索了一下,我是对的。
egorgry 2010年

9

set -e 告诉bash在脚本中任何东西返回非零返回值时退出。

我可以看到这将是多么烦人和麻烦,不确定危险性,除非您已对某些内容开放了权限,并且在可以再次限制它们之前,您的脚本已经死了。


那很有意思。我没想到过早终止的脚本中会打开权限,但我可以看到这很危险。这个测验的有趣之处在于,您不能使用任何参考资料,例如man或google;如果不能完全回答,则根本不要回答。
egorgry 2010年

6
那真是愚蠢,我会重新考虑这个老板...开玩笑(有点)。拥有强大的知识基础是一件好事,但是一半的IT工作是知道/ where /查找信息...希望他们足够聪明,可以在为申请人评分时考虑到这一点。在旁注中,我看到/ no /用于for set -e,实际上,对我而言,它懒惰地忽略了脚本中的错误检查...因此,请与该雇主一起记住这一点...如果他们使用它很多情况下,它们的脚本是什么样的,您将不可避免地需要维护...
cpbills 2010年

好点@cpbills。在脚本中我也看不到set -e的用处,这可能就是为什么它让我如此沮丧的原因。我想发布所有问题,因为有些问题真的很好,有些却像古怪而随意。
egorgry

我已经在许多系统计划工作中看到了它
安德鲁2010年

8

set -e如果遇到非零退出代码,则在某些情况下终止脚本。用几句话总结使用它的危险:它没有表现出人们认为的行为。

在我看来,应该将其视为危险的hack,仅出于兼容性目的而继续存在。该set -e语句不会将Shell从使用错误代码的语言转换为使用类似异常控制流的语言,而只是在尝试模仿这种行为时进行了错误的尝试。

Greg Wooledge对于以下危险有很多话要说set -e

在第二个链接中,存在有关的直观和不可预测行为的各种示例set -e

以下是一些不直观行为的示例set -e(某些摘自上面的Wiki链接):

  • 设置-e
    x = 0
    让x ++
    回声“ x是$ x”
    上面的代码将导致Shell脚本过早退出,因为let x++返回0,该值被let关键字视为虚假值并转换为非零退出代码。set -e注意到这一点,并以静默方式终止脚本。
  • 设置-e
    [-d / opt / foo] && echo“警告:已经安装foo。将覆盖。” >&2
    回声“正在安装foo ...”
    

    上面的方法按预期工作,如果/opt/foo已经存在,则打印警告。

    设置-e
    check_previous_install(){
        [-d / opt / foo] && echo“警告:已经安装foo。将覆盖。” >&2
    }
    
    check_previous_install
    回声“正在安装foo ...”
    

    尽管唯一的区别是单行已重构为函数,但以上内容将终止(如果/opt/foo不存在)。这是因为它本来可以工作的事实是set -e行为的特殊例外。当a && b返回非零值时,它将被忽略set -e。但是,既然它是一个函数,则该函数的退出代码等于该命令的退出代码,并且返回非零的函数将以静默方式终止脚本。

  • 设置-e
    IFS = $'\ n'读取-d''-r -a config_vars <config
    

    上面将从config_vars文件中读取数组config。正如作者可能打算的那样,如果config丢失,它将以错误终止。正如作者可能不希望的那样,如果config不以换行符结尾,则它默默终止。如果set -e此处未使用,则config_vars它将包含文件的所有行,无论文件是否以换行符结尾。

    Sublime Text(以及其他错误处理换行符的文本编辑器)的用户要当心。

  • 设置-e
    
    should_audit_user(){
        本地组groups =“ $(组” $ 1“)”
        用于$ groups中的组;做
            如果[“ $ group” =审计]; 然后返回0; 科幻
        完成
        返回1
    }
    
    如果should_audit_user“ $ user”; 然后
        记录器'Blah'
    科幻
    

    此处的作者可能会合理地期望,如果由于某种原因该用户$user不存在,则该groups命令将失败并且脚本将终止,而不是让用户执行未经审核的某些任务。但是,在这种情况下,set -e终止永远不会生效。如果$user由于某种原因无法找到该should_audit_user函数,则该函数将返回错误的数据,而不是终止该脚本,而不是终止脚本set -e

    这适用于从if语句的条件部分调用的任何函数,无论嵌套的深度如何,定义的位置如何,即使您set -e再次在其内部运行也是如此。if在任何时候使用都会完全禁用set -e条件块完全执行之前的效果。如果作者不了解此陷阱,或者在可以调用函数的所有可能情况下都不了解他们的整个调用堆栈,则他们将编写错误的代码,并且set -e至少部分地将错误代码提供给用户。怪。

    即使作者充分意识到了这一缺陷,解决方法是以与编写不编写代码的方式相同的方式编写代码set -e,从而有效地减少了该开关的浪费。作者不仅必须编写手动错误处理代码(好像set -e没有生效一样),而且的出现set -e可能使他们误以为他们不必这样做。

其他一些缺点set -e

  • 它鼓励草率的代码。错误处理程序完全被遗忘了,希望发生任何故障都会以某种明智的方式报告错误。但是,对于let x++上述示例,情况并非如此。如果脚本意外终止,通常是静默的,这会妨碍调试。如果脚本没有消失,而您希望它能够死掉(请参见前面的要点),那么您手上会有一个更微妙和隐蔽的错误。
  • 它使人们产生一种错误的安全感。再次参见if-condition项目符号点。
  • Shell终止的位置在Shell或Shell版本之间不一致。由于在bash的较早版本set -e之间进行了调整,因此可能意外地编写了在较旧版本的bash上表现不同的脚本。

set -e这是一个有争议的问题,一些意识到它周围问题的人建议反对它,而另一些人则建议在它主动知道陷阱时小心谨慎。有许多shell脚本新手推荐set -e所有脚本作为错误条件的综合解决方案,但在现实生活中,这样做并非如此。

set -e 不能替代教育。


2

我想说这很危险,因为您不再控制他的脚本流动了。只要脚本调用的任何命令返回非零值,脚本就可以终止。因此,您要做的就是做一些改变任何组件的行为或输出的操作,然后终止主脚本。这可能更多是样式问题,但肯定会带来后果。如果您的主脚本应该设置一些标志,但不是因为它提前终止,该怎么办?如果它假定该标志应该存在,或者使用一个意外的默认值或旧值,您最终都会对系统的其余部分造成错误。


完全同意这个答案。这并不是天生的危险,但这是一个意外退出的机会。
pboin 2010年

1
这让我想起了我的C ++教授,因为我在程序中没有单个入口点/单个出口点,所以总是从我的分配中提取点。我以为这纯粹是“原则”,但是这套e生意肯定演示了事情如何以及为什么会完全失控。最终,程序是关于控制和确定性的,如果提前终止,您将同时放弃这两种方法。
Marcin 2010年

0

作为@Marcin回答的一个更具体的例子,它亲自给我带来了困扰,想象一下rm foo/bar.txt脚本中的某行。如果foo/bar.txt实际上不存在,通常没什么大不了的。但是,set -e现在,您的脚本将在那里提前终止!哎呀。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.