程序以状态!= 0(set -e)退出后执行EXIT陷阱时的Bash函数作用域状态

Egi*_*ile 5 bash local readonly bash-trap

在bash函数中声明局部变量使该变量仅在函数本身及其子级内部可见,因此如果我运行:

#!/bin/bash
set -e

func_one() {
  echo "${var}"
}

func_two() {
  local -r var="var from func_two"
  func_one
}

func_two
Run Code Online (Sandbox Code Playgroud)

输出为:

var from func_two
Run Code Online (Sandbox Code Playgroud)

即使var变量声明为local,并且func_two内的只读变量也可以从func_one函数访问。在后者中,可以声明一个具有本地和只读名称的变量:

#!/bin/bash
set -e

func_one() {
  local -r var="var from func_one"
  echo "${var}"
}

func_two() {
  local -r var="var from func_two"
  func_one
}

func_two
Run Code Online (Sandbox Code Playgroud)

输出为:

var from func_one
Run Code Online (Sandbox Code Playgroud)

如果从EXIT陷阱调用func_one,也会发生同样的情况:

#!/bin/bash
set -e

func_one() {                                                                    
  local -r var="var from func_one"                                              
  echo "${var}"                                                                 
}                                                                               

func_two() {                                                                   
  local -r var="var from func_two"                                             
  trap 'func_one' EXIT
  echo "${var}"                                             
}                                                                               

func_two                                                                       
Run Code Online (Sandbox Code Playgroud)

运行我收到的代码:

var from func_two
var from func_one
Run Code Online (Sandbox Code Playgroud)

但是,如果在错误后执行EXIT陷阱(如果命令以非零状态退出,则set -e选项会使脚本立即退出)。似乎无法在func_one内部重新分配var变量:

#!/bin/bash
set -e

func_one() {                                                                    
  local -r var="var from func_one"                                              
  echo "${var}"                                                                 
}                                                                               

func_two() {                                                                   
  local -r var="var from func_two"                                             
  trap 'func_one' EXIT          
  echo "${var}"                                                
  false                                                                         
}                                                                               

func_two                                                                       
Run Code Online (Sandbox Code Playgroud)

运行我收到的代码:

var from func_two
local: var: readonly variable
Run Code Online (Sandbox Code Playgroud)

谁能告诉我为什么会这样?先感谢您。

Wil*_*urn 3

It's a bug in Bash.

When you initially install func_one as an exit handler, Bash invokes it at the end of the script, after func_two has returned. All is well.

When you use a combination of set -e and invoke false from func_one, Bash is exiting the script, and invoking the exit handler, after the call to false, in other words, within func_one.

Bash implements "exit on error" by invoking longjmp to return control to the top level parser, passing the code ERREXIT. In the code that handles this case, there is a comment to the effect that the script should forget about any function that was executing, which it does by setting a variable, variable_context, to 0. It looks like variable_context is an index into a stack of naming scopes, and setting it back to 0 points it to the top-level global scope.

Next, Bash invokes the trap handler, which invokes func_one. Now variable_context is 1, that is, the same value it had within func_two. When the script tries to set var, Bash looks at the names defined in this context and discovers that var is already there, left over from func_two.

I confirmed this in the debugger and also with a workaround: if you add an intermediate function call, the script works, because now within func_one, variable_context is 2 and Bash doesn't see the leftover var from func_two anymore:

#!/bin/bash
set -e

func_one() {
  local -r var="var from func_one"
  echo "${var}"
}

func_intermediate() {
  func_one
}

func_two() {
  local -r var="var from func_two"
  echo "${var}"
  trap 'func_intermediate' EXIT
  false
}

func_two
Run Code Online (Sandbox Code Playgroud)

Apparently within the Bash code unwinding the function call stack involves actually removing the variables (there is a function called kill_all_local_variables); just decrementing variable_context (or setting it to 0) isn't good enough. That's why the script works in the case where func_two returns first and is able to clean up its variables before Bash invokes func_one.

更新:它看起来variable_context不是堆栈的索引(它只是一个函数嵌套计数器),并且代码在进入函数时为变量分配新空间所以不能 100% 确定这里实际发生了什么,但 Bash 确实找到了insidefunc_two的版本,并且添加中间调用使问题消失,所以这是 Bash 在之后没有清理的某种问题varfunc_onefunc_two when leaving it due to the "exit on error" setting and causing func_one to inherit its variables.