【问题标题】:Variable assignment in nested function call unexpectedly changes local variable in the caller's scope嵌套函数调用中的变量赋值意外更改了调用者范围内的局部变量
【发布时间】:2017-01-20 18:26:12
【问题描述】:

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


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

$ f
2

有没有办法禁用这个变量从函数到函数的溢出?

我正在编写一个相当大且重要的 bash/zsh 脚本,该脚本在各种函数中使用了大量变量;其中许多函数依赖于一个更大的主函数,但是由于变量通过一些相当不幸和意外的行为和错误出现在最前沿,使我无法自信地进一步开发,因为我想先解决这个奇怪的问题.

我什至尝试使用local 来本地化变量,但效果仍然存在。

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

【问题讨论】:

  • @Inian,也许吧,但我的问题实际上与为变量赋予新值没有任何关系,而是保留旧值。我查看了您链接的问题中接受的答案和实际问题,但它们似乎并没有真正解决我的问题。

标签: bash function scope zsh


【解决方案1】:

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

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

如果声明局部变量的函数调用另一个函数,则该局部变量将被继承(并且可以修改)。因此,local 保护从较高范围内继承的同名变量的更改,但不保护从较低范围内继承的同名变量。因此必须在每个级别使用local 声明,除非您确实想要更改父作用域中的值。这与大多数编程语言的做法背道而驰,具有优势(快速和脏数据共享),但会产生难以调试的故障模式。

可以使用local -x 导出局部变量以使其可供子进程使用(非常有用),或者在创建时使用local -r 将其设置为只读。

一个不错的技巧是您可以在创建时使用从父范围继承的值来初始化一个变量:

local -r VAR="$VAR"

如果你像我一样总是使用set -u 来避免默默地使用未初始化的变量,并且不能确定变量是否已经被赋值,如果它没有在父级中定义,你可以使用它来初始化它为一个空值范围:

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

【讨论】:

  • 很好的答案。作为一个次要的兴趣点:dashbashzsh 都按照您描述的继承方式行事,而 ksh 是唯一的反对者(除了必须使用 function <name> { ... } 语法而不是 @ 987654334@ 和 typeset 而不是 local),使局部变量真正成为局部变量。然而,initialize-a-local-variable-with-the-inherited-value 技术适用于所有这些方法。
【解决方案2】:

我觉得自己像个白痴,没有早点意识到这一点;无论如何,我将继续发布这个问题和答案,以防其他像我这样的磨砂膏遇到同样的问题:你必须将两个变量都声明为本地

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-20
    • 1970-01-01
    • 1970-01-01
    • 2013-07-13
    • 2013-11-27
    • 2014-03-01
    • 2013-11-27
    相关资源
    最近更新 更多