“set -e” 起什么作用?为什么它被认为是危险的?

“set -e” 起什么作用?为什么它被认为是危险的?

这个问题出现在面试前的测验中,这让我很抓狂。有人能回答这个问题让我放松一下吗?测验没有提到特定的 shell,但职位描述是针对 unix sa 的。问题再次简单...

“set -e” 起什么作用?为什么它被认为是危险的?

答案1

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

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

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

http://www.debian.org/doc/debian-policy/ch-opersys.html9.3.2——

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

从面试官的角度来看,这是一个有效的问题,因为它可以衡量应聘者对服务器级脚本和自动化的实际知识

答案2

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.

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

答案3

需要注意的是,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在特定情况下如何运行。

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

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

我认为危险在于不理解什么时候set -e起作用,什么时候不起作用,并且在某些无效的假设下错误地依赖它。

另请参阅BashFAQ/105 为什么 set -e(或 set -o errexit 或 trap ERR)没有按照我预期的方式运行?

答案4

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

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

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

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

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

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

    上述操作按预期进行,如果/opt/foo已存在则打印警告。

    设置-e
    检查上一个安装() {
        [ -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它将以错误终止。正如作者所期望的那样,如果不是以换行符结尾,它将默默终止。如果此处未使用 ,则将包含文件的所有行,无论它是否以换行符结尾。configconfigset -econfig_vars

    Sublime Text(以及其他不能正确处理换行符的文本编辑器)的用户要小心。

  • 设置-e
    
    应该审核用户(){
        本地组 groups="$(groups "$1")"
        对于 $groups 中的组;执行
            如果 [ “$group” = audit ]; 然后返回 0; fi
        完毕
        返回 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条件要点。
  • set -e不同 shell 或不同版本的 shell 终止的位置并不一致。由于不同版本的行为有所调整,因此可能会意外编写一个脚本,该脚本在旧版本的 bash 上的行为有所不同。

set -e是一个有争议的问题,一些意识到这个问题的人建议不要使用它,而另一些人则建议在它活跃时小心谨慎,了解其中的陷阱。许多 shell 脚本新手建议set -e在所有脚本上都使用它来捕获所有错误情况,但在现实生活中它并不是这样工作的。

set -e不能代替教育。

相关内容