在程序以状态退出后执行 EXIT 陷阱时的 Bash 函数范围状态!= 0(设置 -e)

Posted

技术标签:

【中文标题】在程序以状态退出后执行 EXIT 陷阱时的 Bash 函数范围状态!= 0(设置 -e)【英文标题】:Bash function scope status when EXIT trap is executed after a program exits with status != 0 (set -e) 【发布时间】:2020-03-19 22:58:01 【问题描述】:

在 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

谁能向我解释为什么会发生这种情况?提前谢谢你。

【问题讨论】:

我想说这是不使用set -e 的另一个原因,但由于set -e 是由POSIX 定义的,而localbash 扩展名,这可能是local 的实现中的一个错误。我会注意到,如果您放弃-r 选项并在调用local 之后调用readonly var,那么dash 中的相同代码将按预期工作(它有自己的local 的非标准实现) 但在bash 中产生相同的错误。 【参考方案1】:

这是 Bash 中的一个错误。

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

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

Bash 通过调用longjmp 将控制权返回给***解析器,并传递代码ERREXIT 来实现“出错时退出”。在处理这种情况的代码中,有一条注释表明脚本应该忘记正在执行的任何函数,它通过将变量variable_context 设置为0 来实现这一点。看起来variable_context 是一组命名范围的索引,将其设置回0 会将其指向***全局范围。

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

我在调试器中确认了这一点,并且还使用了解决方法:如果添加中间函数调用,则脚本可以工作,因为现在 func_onevariable_context2,而 Bash 看不到剩余的 @ 987654344@ 来自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 确实在 var 内部找到了 func_two 版本 func_one,并且添加中间调用使问题消失,所以这是 Bash 不清理的某种问题在func_two 之后由于“错误退出”设置而离开它并导致func_one 继承其变量。

【讨论】:

本来打算去的,但是才30分钟。该错误已存在至少 12 年,因此再多几个小时可能不会有太大影响。 @WillisBlackburn 非常感谢您的回答。如果我理解正确,目前的选择是 1)使用解决方法或 2)避免使用 set -e。 我认为您也可以为 ERR 设置陷阱处理程序,然后退出该处理程序。

以上是关于在程序以状态退出后执行 EXIT 陷阱时的 Bash 函数范围状态!= 0(设置 -e)的主要内容,如果未能解决你的问题,请参考以下文章

Shell命令的退出状态及错误检查

linux退出状态码及exit命令

linux 用户退出怎么命令

linux 用户退出怎么命令

OSX - 如何在“退出”命令执行后自动关闭终端窗口。

Linux命令之退出exit