在 dash(以及 bash zsh 和其他一些 shell)中,该命令的local
作用是将变量范围限制为该函数(在某些情况下还包括子函数)。这使得可以将变量限制为该函数内部结构仅(以及在某些情况下的后代函数调用)。
例如:
testlocal(){
local IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
testdescend(){
echo "descended IFS = $IFS"
}
IFS=abc
testlocal
echo "external IFS = $IFS"
在 dash(以及 bash 和 zsh)中执行时产生以下输出:
$ dash ./script
internal IFS = 123
descended IFS = 123
external IFS = abc
这意味着 IFS 仍然存在当地的到函数(和后代)。
然而,ksh 不同,不接受local
:
$ ksh ./script
./mt2[3]: local: not found [No such file or directory]
将“local”一词更改为“typeset”使脚本(几乎)可以在 ksh 中运行,但 dash 无法识别typeset
。
function
并且,为了使 ksh 中的范围动态(下降到被调用的函数),必须使用该词来定义函数:
function testlocal {
typeset IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
这使得 Dash 的可移植性进一步复杂化。
问题是:如何使原始脚本也在 ksh 中工作?是否可以避免测试哪个 shell 正在运行脚本?
答案1
local
请注意,ksh88 及其所有克隆都执行动态作用域,并且至少从 1990 年的 ksh88 和 1994 年的 ksh88 pdksh
(以及 1989 年bash
(现在)实现了许多 ksh88 API)开始支持它。
您ksh
所指的是ksh93
David Korn 从头开始的一个较新的实现,其 API 略有不同且不兼容。
最好将该 shell 称为API,ksh93
而不是仅仅ksh
指ksh
几ksh88
十年来的 API(除了 ksh93 实现之外的所有实现)。ksh93
直到 2000 年之后几年,其代码作为开源发布,才得到广泛使用。
ksh93 的typeset
唯一作用静止的范围界定。 POSIX 反对指定 ksh88 的typeset
/ ,local
因为它是动态作用域(尽管如此,尽管大多数语言(如 C)都这样做)静止的范围界定,动态的范围界定在 shell 中更自然,因为它是您通过子 shell 或环境获得的效果),这解释了为什么 ksh93 被重写为静态应对。
后来,大多数其他 shell 都实现了 ksh88 的作用域,因此 ksh93 现在是例外。现在讽刺的是,POSIX 唯一合理的选择是指定动态范围。虽然 David Korn 最初拒绝在 ksh93 中实现动态作用域,但他曾表示可以使用local
POSIX 邮件列表上的关键字/builtin 来考虑它,但在这一切完全实现之前他已经退休了。
AT&T 的 ksh93v- beta 和最终版本可以使用实验性“bash”模式(实际上默认启用)进行编译,该模式在作为 调用时执行动态作用域(以函数形式,包括 withlocal
和typeset
)。ksh93
bash
bash
ksh2020 中默认禁用该模式尽管即使未编译 bash 模式,local
/别名declare
也会保留typeset
(尽管仍然具有静态范围)。
现在,如果我们将 beta 版本及其 bash 模式放在一边,ksh93仅有的执行静态作用域,并且仅在使用 ksh 样式语法 ( ) 声明的函数中进行function name { code; }
。您会感到困惑,因为使用 Bourne 风格语法 ( ) 声明的函数f() command
不执行作用域根本不。这与使用 调用的源文件或 ksh 函数相同. name [args]
。在这些中,typeset
不会在函数的作用域中声明新变量(该函数没有作用域),它只是更新当前作用域中变量的类型,该变量的类型将是全局作用域或 ksh- 的作用域style 函数,如果 Bourne 风格(最终)是从 ksh 风格函数中调用的。
Bourne 风格函数的代码在调用时就像嵌入/复制粘贴/来源一样运行。
var=global
function ksh_function {
typeset var=private
echo "ksh1: $var"
bourne_function
echo "ksh2: $var"
other_ksh_function other
echo "ksh3: $var"
. other_ksh_function other_invoked_with_dot
echo "ksh4: $var"
}
bourne_function() {
typeset var=set-from-bourne-function
}
function other_ksh_function {
echo "other: $var"
var=$1
}
ksh_function
echo "global: $var"
给出:
ksh1: private
ksh2: set-from-bourne-function
other: global
ksh3: set-from-bourne-function
other: set-from-bourne-function
ksh4: other_invoked_with_dot
global: other
在 ksh93 中,除了使用子 shell 或自己实现变量堆栈之外,不可能具有动态作用域,如locvar
概念证明或者你出口要传递给每个命令的变量(包括使用 ksh 样式函数声明的函数,包括通过环境的外部命令)。
在您的特定情况下,只有testlocal
函数(而不是testdescend
)需要局部作用域,您可以使用所述的shdef
+方法kshdef
那里或者做类似的事情:
case $KSH_VERSION in
(*" 93"*)
fn_with_local_scope() {
alias local=typeset
eval "function $1 {
$(cat)
}"
}
;;
(*)
fn_with_local_scope() {
eval "$1() {
$(cat)
}"
}
;;
esac
然后将您的函数声明为:
fn_with_local_scope testlocal << '}'
local IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
testdescend(){
echo "descended IFS = $IFS"
}
IFS=abc
testlocal
echo "external IFS = $IFS"
(并且仅local
在使用 声明的函数中使用fn_with_local_scope
)。
这使
internal IFS = 123
descended IFS = 123
external IFS = abc
在所有 shell 中(请注意,您需要最新版本yash
(2.48 或更高版本)才能支持local
)。
或者,如果您同意导出局部变量(仅限 ksh93):
case $KSH_VERSION in
(*" 93"*)
fn() {
alias local='typeset -x'
eval "function $1 {
$(cat)
}"
}
;;
(*)
fn() {
eval "$1() {
$(cat)
}"
}
;;
esac
fn testlocal << '}'
local IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
fn testdescend << '}'
echo "descended IFS = $IFS"
}
IFS=abc
testlocal
echo "external IFS = $IFS"
现在,如果您要使用 C 或 ksh93 等具有静态作用域的语言执行类似的操作,您将执行以下操作:
function testlocal {
typeset IFS
IFS=123
echo "internal IFS = $IFS"
testdescend "$IFS"
}
function testdescend {
typeset IFS="$1" # explicitly get the value $IFS from the caller
echo "descended IFS = $IFS"
}
在我看来,这是一个更好的设计,并且该代码在执行动态作用域的 shell 中也可以正常工作(您仍然需要解决不同的函数定义语法)。
进一步阅读:
答案2
我更喜欢 bash(我认为更好的数组语法),但有时会遇到 ksh 脚本,因此我尝试在方便时执行这些操作:
始终使用function func_name { body; }
语法。
基于上述,要local
在第一个函数之前使用关键字:,请使用:
# teach ksh 93 about local
case "$KSH_VERSION" in *' 93'*) alias local='typeset -x' ;; esac
要获取函数内的函数名称:使用:
local MYNAME="${FUNCNAME[0]:-$0}"