嵌套函数调用中的变量赋值意外地更改了调用者范围

时间:2017-01-20 18:26:12

标签: bash function scope zsh

编者注
也许以下内容取自OP自己的答案,更好地说明了令人惊讶的行为:
f() { local b=1; g; echo $b; }; g() { b=2; }; f # -> '2'
即,g()能够修改f()本地 $b变量。 功能

在Zsh和Bash中,如果我运行f() { a=1; g; echo $a; }时有以下函数g() { a=2; }和以下函数f,我会得到以下输出而不是预期的输出:

$ f
2

无论如何都要禁用从函数到函数的这个变量渗透?

我正在研究一个相当大而重要的bash / zsh脚本,它在各种函数中使用了大量的变量;许多这些功能依赖于更大的主功能,但是由于变量通过一些相当不幸和意外的行为而流失,并且错误已经成为最重要的,阻止我自信地进一步发展,因为我想首先解决这个奇怪的问题。

我甚至尝试使用local来定位变量,但效果仍然存在。

编辑:请注意,我的问题不是关于如何使用局部变量来防止变量渗透或关于局部变量如何工作,如何设置局部变量,如何为已经声明的局部变量赋值,或者任何废话:它是关于如何防止变量渗入调用者/被调用函数的范围。

2 个答案:

答案 0 :(得分:7)

使用local创建一个不从父作用域继承的变量。

有一些有用的东西需要补充。

如果声明的函数调用另一个函数,则将继承(并且可以修改)局部变量。因此,local保护对范围内从较高位继承的同名变量的更改,但不保留范围内的更低。因此,必须在每个级别使用local声明,除非您实际上想要更改父作用域中的值。这与大多数编程语言的作用相反,并且具有优势(快速和脏的数据共享),但却难以调试失败模式。

可以使用local -x导出局部变量以使其可用于子流程(非常有用),或者在使用local -r创建时只读取。

一个很好的技巧是你可以在创建时使用从父作用域继承的值初始化变量:

local -r VAR="$VAR"

如果像我一样,你总是使用set -u来避免静默使用未初始化的变量,并且不能确定已经分配了变量,那么你可以使用它来用空值初始化它,如果它没有在父范围:

local -r VAR="${VAR-}"

答案 1 :(得分:1)

我觉得自己很快就没有意识到这一点;我要继续发表这个问题&无论如何,为了防止像我这样的其他磨砂遇到同样的问题:你必须将两个变量声明为本地

f() { local b=1; g; echo $b; }
g() { b=2; }
f
# output: 2

f() { local b=1; g; echo $b; }
g() { local b=2; }
f
# output: 1