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

这个问题已经出现在面试前的测验,这让我疯狂。 任何人都可以回答这个问题,让我安心吗? 测验没有提到特定的shell,但是工作描述是针对unix sa的。 再次,问题是简单的…

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

set -e会导致shell在任何子命令或pipe道返回非零状态时退出。

面试官可能要找的答案是:

创buildinit.d脚本时使用“set -e”会很危险:

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

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

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

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. 

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

请记住,这是一个面试的测验。 这些问题可能是现在的员工写的,可能是错的。 这不一定是坏事,每个人都会犯错误,面试问题经常坐在黑暗的angular落里,没有经过审查,只能在面试时出来。

“set -e”完全可以做任何我们认为是“危险”的事情。 但是,这个问题的作者可能会错误地认为,由于自己的无知或偏见,“定”是危险的。 也许他们写了一个错误的shell脚本,它轰炸得可怕,而且他们错误地认为“set -e”是应该的,实际上他们忽略了正确的错误检查。

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

或者这可能是个狡猾的问题,这可能是蹩脚的,但并不完全出乎意料。

或者,也许这是一个很好的解释: http : //www.mail-archive.com/[email protected]/msg473314.html

应该注意的是, set -e可以打开和closures脚本的各个部分。 对于整个脚本的执行,不一定非要执行。 它甚至可以有条件启用。 这就是说,我从来没有使用它,因为我做我自己的error handling(或不)。

 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 -efunction。

如果失败的命令是紧接着一段时间或直到关键字,在if语句中的testing的一部分,在&&或列表中执行的命令的一部分,或者命令的返回值正在通过!倒置。 这些都是errexit选项所遵循的相同条件。

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

我认为危险在于set -e进入时,不知道什么时候不了解,在某种无效的假设下错误地依赖。

请参阅BashFAQ / 105为什么没有设置-e(或设置-er errexit,或陷阱ERR)做我的预期?

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

我可以看到那将会是多么恼人,而且越野车,不知道危险,除非你打开了某些东西的权限,而在你再次限制它们之前,脚本就死掉了。

我会说这是危险的,因为你不再控制脚本的stream动。 只要脚本调用的任何命令返回非零值,脚本就可以终止。 所以你所要做的就是做一些改变任何组件的行为或输出的东西,并且你可以终止主脚本。 这可能是更多的风格问题,但它肯定有后果。 如果你的主要脚本应该设置一些标志,而不是因为它提前终止? 如果假设该标志应该在那里,或者使用意外的默认值或旧值,那么最终会导致系统的其余部分发生故障。

set -e在遇到非零退出代码时终止脚本,除了在某些情况下。 用几句话总结一下它的用法的危害:它并不像人们想象的那样行事。

在我看来,它应该被认为是一个危险的黑客,它只是为了兼容性而继续存在。 set -e语句不会将shell从使用错误代码的语言转换为使用类似exception的控制stream的语言,而只是很less尝试模拟该行为。

Greg Wooledge在set -e的危险方面有很多话要说:

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

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

  •  设置-e
     x = 0的
    让x ++
    回声“x是$ x” 

    以上操作将导致shell脚本过早退出,因为let x++返回0,将let关键字视为falsy值,并将其转换为非零退出代码。 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 ...”
    

    上面,尽pipe唯一的区别是一行被重构成一个函数,如果/opt/foo不存在,将终止。 这是因为它最初起作用的事实是set -e行为的一个特例。 当a && b返回非零时,它会被set -e忽略。 但是,现在它是一个函数,函数的退出代码等于该命令的退出代码,返回非零的函数将静默终止脚本。

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

    以上将从文件config读取数组config_vars 。 正如作者可能打算的那样,如果缺lessconfig ,它将终止一个错误。 由于作者可能不打算,如果config不以换行符结束,它会默默终止。 如果在这里不使用set -e ,那么config_vars将包含文件的所有行, config_vars是否以换行符结束。

    崇高文本的用户(和其他文本编辑器错误地处理新行),请注意。

  • 设置-e
    
     should_audit_user(){
        本地群组=“$(groups”$ 1“)”
        为$组的团体; 做
            如果[“$ group”= audit]; 然后返回0; 科幻
         DONE
        返回1
     }
    
    如果should_audit_user“$ user”; 然后
        logging器'Blah'
    科幻
    

    这里的作者可能会合理地预期,如果由于某种原因用户$user不存在,那么groups命令将失败,脚本将终止而不是让用户执行一些未经审计的任务。 但是,在这种情况下, set -e终止不会生效。 如果由于某种原因找不到$user ,而不是终止脚本,那么should_audit_user函数就会返回不正确的数据,就像set -e没有生效一样。

    这适用于从if语句的条件部分调用的任何函数,无论嵌套的深度如何,无论定义在哪里,即使再次运行set -e也是如此。 if在任何时候使用, if完全禁用set -e的效果,直到条件块完全执行。 如果作者没有意识到这个缺陷,或者在所有可能调用函数的情况下都不知道他们的整个调用堆栈,那么他们将编写错误的代码, set -e提供的错误的安全意识将会在至less部分是由于责备。

    即使作者完全意识到了这个缺陷,解决方法是编写代码的方式与编写代码相同,不需要set -e ,从而使该开关的效率低于无用; 不仅作者必须编写人工error handling代码,就好像set -e没有效果一样,但set -e的存在可能会使他们认为他们不需要这样做。

set -e另外一些缺点:

  • 它鼓励潦草的代码。 error handling程序完全被遗忘,希望任何失败都会以某种合理的方式报告错误。 但是,像上面的let x++这样的例子,情况并非如此。 如果脚本意外死亡,通常是默默的,这会妨碍debugging。 如果脚本没有死,而且你期望(见前面的要点),那么你的手上就会有一个更微妙和更隐秘的错误。
  • 它使人们陷入一种错误的安全感。 再次查看if -condition项目符号。
  • shell或shell版本之间的shell终止位置不一致。 由于在这些版本之间调整了set -e的行为,可能意外地编写了一个在旧版本的bash上performance不同的脚本。

set -e是一个有争议的问题,有些人意识到围绕它的问题build议反对它,而另一些人只是build议小心,而它是活跃的知道陷阱。 有许多shell脚本新手谁build议在所有脚本上set -e作为错误条件的一个捕获所有,但在现实生活中,它不会这样工作。

set -e不能代替教育。

作为@ Marcin的答案的一个更具体的例子,这个例子已经亲自把我咬了,想象你的脚本中有一个rm foo/bar.txt行。 通常没有什么大不了的,如果foo/bar.txt实际上不存在。 但是,现在set -e现在你的脚本将在那里提前终止! 哎呀。