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

Bash function scope status when EXIT trap is executed after a program exits with status != 0 (set -e)

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

#!/bin/bash
set -e

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

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

func_two

输出为:

var from func_two

即使var变量在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

输出为:

var from func_one

如果从 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                                                                       

运行 我收到的代码:

var from func_two
var from func_one

但是,如果在错误后执行 EXIT 陷阱(如果命令以非零状态退出,则设置 -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                                                                       

运行 我收到的代码:

var from func_two
local: var: readonly variable

任何人都可以向我解释为什么会这样吗?提前谢谢你。

这是 Bash 中的错误。

当您最初将 func_one 安装为退出处理程序时,Bash 在脚本末尾调用它,在 func_two 已 returned 之后。一切顺利。

当您使用 set -e 的组合并从 func_one 调用 false 时,Bash 退出脚本并调用退出处理程序,在调用false,换句话说,在 func_one.

Bash 通过调用 longjmp 到 return 控制到顶级解析器,传递代码 ERREXIT 来实现 "exit on error"。在处理这种情况的代码中,有一条注释表明脚本应该忘记任何正在执行的函数,它通过将变量 variable_context 设置为 0 来实现。看起来 variable_context 是一堆命名范围的索引,将其设置回 0 将其指向顶级全局范围。

接下来,Bash 调用陷阱处理程序,后者调用 func_one。现在 variable_context1,即它在 func_two 中的值相同。当脚本尝试设置 var 时,Bash 查看在此上下文中定义的名称并发现 var 已经存在,是 func_two.[=47= 遗留下来的]

我在调试器中确认了这一点,并且还有一个解决方法:如果你添加一个中间函数调用,脚本就可以工作,因为现在在 func_one 中,variable_context2 并且Bash 不再看到 func_two 剩下的 var:

#!/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

显然在 Bash 代码中展开函数调用堆栈涉及实际删除变量(有一个名为 kill_all_local_variables 的函数);仅递减 variable_context(或将其设置为 0)还不够好。这就是为什么脚本在 func_two return 的情况下工作并且能够在 Bash 调用 func_one.

之前清理其变量的原因

更新:看起来 variable_context 而不是 堆栈的索引(它只是一个函数嵌套计数器),代码 mallocs new space 输入函数时的变量?所以不是 100% 确定这里到底发生了什么,但是 Bash 确实在 func_one 中找到了 varfunc_two 版本,并且添加中间调用可以使问题消失,因此,由于 "exit on error" 设置并导致 func_one 继承其变量,Bash 在 func_two 之后未清理是某种问题。