无法拥有与全局只读变量同名的局部变量

无法拥有与全局只读变量同名的局部变量
#!/bin/bash

readonly x=2

function test {
 local x=1
 echo ${x}
}

test
echo $x

结果是,

readonly-local-test.sh: line 6: local: x: readonly variable
2
2

当变量是只读时,就会发生这种情况。但是,如果我删除只读限制,如下所示,

#!/bin/bash

x=2
function test {
 local x=1
 echo ${x}
}

test
echo $x

结果是,

1
2

为什么只读全局变量不能被隐藏?

答案1

bash好像不能shadow全球的只读变量。我怀疑这是 Posix 除了$@.然而,当地的只读变量可以被隐藏。由于 Posix 中没有局部变量,因此它们的任何行为都是 posix 一致的。

readonly始终定义全局变量。可以使用local -r或在函数内部定义局部只读变量declare -r(行为类似于local函数内部)。

您可以修复您的脚本,将所有全局变量转换为局部变量*。例如,将所有内容包装在里面main() { YOUR_SCRIPT_HERE; }; main并全部替换readonlylocal -r

#! /usr/bin/env bash
main() {
  local -r x=constant
  declare -p x
  f() {
    local x=mutable1
    declare -p x
    x=mutable2
    declare -p x
  }
  f
  declare -p x
  # x=... would not work here
}
main

当然,而不是天真的修复“将所有的里面的脚本main您还可以在外部定义函数main以获得更好的结构:

#! /usr/bin/env bash
main() {
  local -r x=constant
  declare -p x
  f
  declare -p x
  # x=... would not work here
}
f() {
  local x=mutable1
  declare -p x
  x=mutable2
  declare -p x
}
main

* 请注意,在这两个版本中,当从 调用时, main's x(以前是全局变量)位于内部范围内。因此,内部的局部变量的行为大多与实际的全局变量相似。如果您习惯像.fmainmainC例子:

g() { local x=1; h; echo "g.x = $x"; }
h() { x=2; }
g
echo "global.x = $x";

… 印刷

g.x = 2
global.x =

答案2

据我所知,这种行为可以命名为“工作作为设计”。您有只读变量,并且您无法更改该变量的值。在 bash 中,您无法选择定义覆盖此类全局变量集的变量。

答案3

虽然在某些情况下这绝对是令人讨厌的行为,例如在OP中,但我明白这是一个很棒的功能。考虑一个全局只读变量应该就是这样。一旦设定,到处在脚本中该变量具有已知值。这包括任何深度的函数以及源文件带来的任何内容。

尽管这可能会产生所描述的冲突,但“通常”的缓解方法是使用命名约定来区分两种类型:ALL_CAPS_READONLY_GLOBALvs.snake_case_localcamelCaseLocal

可写全局变量是真正的问题。我喜欢使用前导“_”(下划线)来表示它们是“特殊的”并且不应该被粗心地修改。事实上,我通常更喜欢为此类值提供 getter/setter 函数,如下所示:

declare -g _GLOBAL_FOO="default-value"

fooGet() { printf "%s"  "${_GLOBAL_FOO}"; }
fooSet() { _GLOBAL_FOO="${1:-}"; }

对于数组,它更加复杂,但是这个想法可以扩展到索引和关联类型。另外,我实际上并没有做fooGet()上面所示的事情;相反,我提供变量的名称来接收感兴趣的值。因此

local myVar=$(fooGet)             ## okay, but...
local myVar; fooGet --var myVar   ## nicer
fooGet --var myVar                ## works, sort-of, but avoid this!!

实际上,这两种fooGet方法在单个函数中运行良好。如果--var VARNAME提供了参数,则通过 nameref 设置该值。否则,输出支持表单的值$(foo)

此外,如果在初始化/配置/启动期间需要全局可写,则添加另一个函数,例如:

fooFreeze() { declare -ga _GLOBAL_FOO; }

“锁定它”(即使其只读,同时保持当前值)。这在开始时允许灵活性,然后在初始化完成后使值不可变(且不可覆盖)。

相关内容