嵌套函数调用中的变量赋值意外地改变了调用者作用域中的局部变量。

6
编辑说明:
也许下面来自原帖回答的内容更能说明这种令人惊讶的行为:
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来本地化变量,但效果仍然存在。
编辑:请注意,我的问题不是关于如何使用本地变量来防止变量溢出或关于本地变量的工作原理、如何设置本地变量、如何为已声明的本地变量分配新值等等:它是关于如何防止变量流入调用/被调用函数的范围之内。

@Inian,也许是这样,但我的问题实际上与给变量赋新值没有太多关系,而更多地是保留旧值。我查看了您提供的链接中的被接受答案和实际问题,它们似乎并没有解决我的问题。 - Alexej Magura
2个回答

12

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

还有一些有用的东西可以添加。

如果声明该变量的函数调用另一个函数,则将继承本地变量(并且可以修改)。因此,local保护同名变量从更高范围继承的更改,但不保护低范围内的同名变量。因此,在每个级别上必须使用local声明,除非你确实想要更改父级作用域中的值。这与大多数编程语言所做的相反,并具有优点(快速且简单的数据共享),但会创建难以调试的故障模式。

可以使用local -x导出本地变量以使其可由子进程使用(非常有用),或者在创建时使用local -r进行只读设置。

一个不错的技巧是可以通过初始化变量为从父级作用域继承的值来进行创建:

local -r VAR="$VAR"

如果你和我一样,总是使用set -u来避免默默地使用未初始化的变量,并且无法确定变量是否已经分配了值,那么你可以使用以下代码在父级范围内初始化为空值,以便确保它被定义:

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

1
很棒的回答。有趣的是:dashbashzsh在继承方面的行为都与您描述的相同,而ksh则是唯一的反对者(除了必须使用function <name> { ... }语法而不是<name>() { ... }以及typeset而不是local),使本地变量真正成为局部的。然而,在所有这些Shell中,都可以使用从继承值初始化本地变量的技巧。 - mklement0

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接