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)
谁能告诉我为什么会这样?先感谢您。
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.