题 'set -e'做了什么,为什么它会被认为是危险的?


这个问题出现在面试前的测验中,这让我很疯狂。任何人都可以回答这个并让我放心吗?测验没有引用特定的shell,但作业描述是针对unix sa的。 问题再简单......

'set -e'做了什么,为什么它会被认为是危险的?


141
2018-05-19 16:39




这是一个相关的主题: stackoverflow.com/questions/64786/error-handling-in-bash/... - Stefan Lasiewski


答案:


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

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

在创建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,而是分别检查每个命令的结果。

从面试官的角度来看,这是一个有效的问题,因为它衡量的是候选人在服务器级脚本和自动化方面的工作知识


146
2017-08-09 21:01



很好找。谢谢。 - egorgry
好点子。它会在技术上可能出错的情况下停止引导过程,但不应该导致整个脚本停止 - Matt
虽然我同意 stackoverflow.com/questions/78497/...,我认为这种风格在执行目标工作负载之前很适合使用bash进行短初始化检查和日志记录或其他环境猴子修补。我们对docker image init脚本使用相同的策略。 - joshperry


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.

不幸的是,我没有足够的创造力去思考它为什么会有危险,除了“脚本的其余部分不会被执行”或“它可能掩盖真正的问题”。


33
2018-05-19 16:43



掩盖真实/其他问题,是的,我想我能看到......我正在努力想出一个危险的例子...... - cpbills
有联机帮助页 set!我总是最终到了 builtin(1)。谢谢。 - Jared Beck
我怎么知道文档的 set 是要发现的 man 1 bash??怎么会有人找到 -e 选项 set 手册页中的关键字是如此之大?你不能 /-e 在这里搜索 - Blauhirn
@Blauhirn使用 help set - Blauhirn


应当指出的是 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

另外值得注意的是这个来自Bash手册页的部分 trap 命令也描述了如何 set -e 在某些情况下的功能。

该                 如果失败的命令是其中一部分,则不执行ERR陷阱                 命令列表紧跟一段时间或直到关键字,                 if语句中的部分测试,是执行命令的一部分                 在&&或⎪⎪列表中,或者如果命令的返回值正在                 倒过来!这些是相同的条件                 errexit选项。

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

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

还请看 BashFAQ / 105为什么不设置-e(或设置-o errexit,或陷阱ERR)做我所期望的?


19
2018-05-19 22:39



虽然偏离了这个问题,但这个答案正是我正在寻找的:只关闭脚本的一小部分! - Stephan Bijzitter


请记住,这是面试的测验。这些问题可能是由现任工作人员撰写的,而且可能是错误的。这并不一定是坏事,每个人都会犯错误,面试问题经常在没有审查的情况下坐在黑暗的角落里,而且只会在面试时出现。

'set -e'完全有可能不做任何我们认为“危险”的事情。但是这个问题的作者可能错误地认为'set -e'是危险的,因为他们自己的无知或偏见。也许他们写了一个有问题的shell脚本,它被轰炸得很厉害,他们错误地认为'set -e'应该受到指责,而实际上他们却忽略了编写正确的错误检查。

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

或许这是一个技巧问题,这将是蹩脚的,但并非完全出乎意料。

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


13
2018-05-19 17:03



+1我在同一条船上,我一直在处理的很多“技术”采访至少被误导了。 - cpbills
哇。好好找到debian列表。 +1对于面试问题的无知。我记得因为我百分百肯定我是对的,因此争论一次netback的答案。他们说没有。我回家用Google搜索,我是对的。 - egorgry


set -e 告诉bash,在脚本中,只要有任何东西返回非零返回值就退出。

我可以看到这将是多么令人烦恼,并且错误,不确定危险,除非你已经打开了某些东西的权限,并且在你再次限制它们之前,你的脚本就会死亡。


9
2018-05-19 16:46



那很有意思。我没有想到过早死亡的脚本中打开的权限,但我可以看到被认为是危险的。这个测验的有趣部分是你不能使用任何参考资料,如man或google,如果你不能完全回答,根本不回答它。 - egorgry
这只是愚蠢,我会重新考虑这个雇主......开玩笑(有点)。拥有强大的知识基础是件好事,但IT工作的一半是知道/在哪里/找到信息......希望他们足够聪明,在评分申请人时考虑到这一点。在旁注,我看/不/用于 set -e事实上,对我而言,它忽略了在你的脚本中忽略错误检查的懒惰...所以请记住,与这个雇主一样......如果他们经常使用它,他们的脚本是什么样的,你将不可避免地要维持...... - cpbills
优点@cpbills。我也看到在脚本中没有使用set -e,这可能就是为什么它让我如此困扰。我想发布所有问题,因为有些问题非常好,而且有些问题非常古怪和随意。 - egorgry
我在许多系统cron工作中都看到了它...... - Andrew


set -e 如果遇到非零退出代码,则终止脚本,但在某些条件下除外。用几句话总结其使用的危险性:它不像人们认为的那样。

在我看来,它应该被视为一种危险的黑客,它只是出于兼容性目的而继续存在。该 set -e 语句不会将shell从使用错误代码的语言转换为使用类似异常的控制流的语言,它只是很难尝试模拟该行为。

Greg Wooledge有很多关于危险的说法 set -e

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

一些非直观行为的例子 set -e (一些取自上面的wiki链接):

  • 设置-e
    x = 0的
    让x ++
    echo“x is $ x”
    以上将导致shell脚本过早退出,因为 let x++ 返回0,由...处理 let 关键字作为假值并变为非零退出代码。 set -e 注意到这一点,并默默地终止脚本。
  • 设置-e
    [-d / opt / foo] && echo“警告:已经安装了foo。将覆盖。” >&2
    echo“安装foo ......”
    

    以上工作如预期,如果打印警告 /opt/foo 已存在。

    设置-e
    check_previous_install(){
        [-d / opt / foo] && echo“警告:已经安装了foo。将覆盖。” >&2
    }
    
    check_previous_install
    echo“安装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(){
        本地组组=“$(组”$ 1“)”
        对于$ groups组;做
            if [“$ group”= audit];然后返回0;科幻
        DONE
        返回1
    }
    
    if 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

  • 它鼓励邋code的代码。错误处理程序完全被遗忘,希望无论何种失败都会以某种合理的方式报告错误。但是,举例如 let x++ 以上情况并非如此。如果脚本意外死亡,它通常是静默的,这会妨碍调试。如果脚本没有死亡并且你期望它(参见上一个要点),那么你手上就会有一个更微妙和阴险的错误。
  • 它导致人们产生虚假的安全感。再看一下 if条件要点。
  • shell终止的位置在shell或shell版本之间不一致。由于行为的原因,可能会意外地编写一个脚本,该脚本在旧版本的bash上表现不同 set -e 在这些版本之间进行了调整。

set -e 是一个有争议的问题,有些人意识到它周围的问题建议反对它,而其他一些人只是建议在积极的时候注意,以了解陷阱。有许多shell脚本新手推荐 set -e在所有脚本上作为错误条件的全能,但在现实生活中它不会那样工作。

set -e 不能替代教育。


5
2018-04-27 23:06





我说这很危险,因为你不再控制你的剧本流程了。只要脚本调用的任何命令返回非零,脚本就可以终止。因此,您所要做的就是做一些改变任何组件的行为或输出的事情,然后终止主脚本。它可能更像是一种风格问题,但肯定会产生影响。如果你的主要剧本应该设置一些标志,而不是因为它提前终止了怎么办?如果它假定标志应该在那里,或者使用意外的默认值或旧值,那么你最终会破坏系统的其余部分。


2
2018-05-19 18:07



完全同意这个答案。不是 本质 危险,但这是一个意外提前退出的机会。 - pboin
这让我想起的是我的一个C ++教授,因为程序中没有单个入口点/单个出口点,所以总是从我的任务中提取点数。我认为这纯粹是一个“原则”的事情,但这套商业无疑会演示如何以及为什么事情完全失控。最终程序是关于控制和决定论的,并且过早终止你放弃两者。 - Marcin


作为@ Marcin的回答的一个更具体的例子,我个人咬了我,想象有一个 rm foo/bar.txt 在你的脚本中的某个地方。通常没什么大不了的 foo/bar.txt 实际上并不存在。然而 set -e 现在你的脚本将在那里提前终止!哎呀。


0
2018-06-22 19:00