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

时间:2019-11-24 14:05:04

标签: 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

输出为:

var from func_two

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

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

如果从退出陷阱中调用 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陷阱(如果命令以非零状态退出,则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                                                                       

运行我收到的代码:

var from func_two
local: var: readonly variable

任何人都可以告诉我为什么会这样吗?预先谢谢你。

1 个答案:

答案 0 :(得分:2)

这是Bash中的错误。

最初安装func_one作为出口处理程序时,Bash在返回func_two后在脚本末尾调用它。一切都很好。

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

Bash通过调用longjmp并传递代码ERREXIT来调用variable_context,以将控制权返回给顶级解析器,从而实现“错误退出”。在处理这种情况的代码中,有一段注释表明脚本应该忘记正在执行的任何功能,这是通过将变量0设置为variable_context来实现的。似乎0是指向命名作用域堆栈的索引,将其设置回func_one会将其指向顶级全局作用域。

接下来,Bash调用陷阱处理程序,该陷阱处理程序调用{​​{1}}。现在variable_context1,也就是说,它与func_two中的值相同。当脚本尝试设置var时,Bash查看在此上下文中定义的名称,并发现var剩下的func_two已经存在。

我已在调试器中确认了这一点,并提供了一种解决方法:如果您添加一个中间函数调用,该脚本将起作用,因为现在在func_one中,variable_context2,而Bash没有不再看到var剩下的func_two

#!/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首先返回并能够在Bash调用func_one之前清理其变量的情况下起作用的原因。

更新:看来variable_context并不是堆栈中的索引(只是函数嵌套计数器),并且代码在输入函数时为变量分配了新空间?因此,不能100%确定这里的实际情况,但是Bash确实在func_two内找到了var的{​​{1}}版本,并且添加了中间调用使问题消失了,所以这有点Bash在func_one之后由于“错误退出”设置而无法清除并导致func_two继承其变量的问题而没有清理。