ImH*_*ere 5 bash ksh zsh dash shell-script
在 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"
Run Code Online (Sandbox Code Playgroud)
在 dash(以及 bash 和 zsh)中执行时生成此输出:
$ dash ./script
internal IFS = 123
descended IFS = 123
external IFS = abc
Run Code Online (Sandbox Code Playgroud)
这意味着 IFS对函数(和后代)来说仍然是本地的。
但是, ksh 不同,不接受local:
$ ksh ./script
./mt2[3]: local: not found [No such file or directory]
Run Code Online (Sandbox Code Playgroud)
将“本地”一词更改为“排版”可使脚本(几乎)在 ksh 中工作,但 dash 无法识别typeset.
并且,为了使 ksh 中的范围动态(下降到被调用的函数),function必须使用这个词来定义函数:
function testlocal {
typeset IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
Run Code Online (Sandbox Code Playgroud)
这进一步使破折号的便携性复杂化。
问题是:如何使原始脚本也在 ksh 中工作?是否可以避免测试哪个 shell 正在运行脚本?
local请注意,ksh88 及其所有克隆都执行动态作用域,并且至少从 1990 年的 ksh88 和 1994 年的 ksh88 pdksh(以及 1989 年bash(现在)实现了许多 ksh88 API)开始支持它。
您ksh所指的是ksh93David Korn 从头开始的较新实现,其 API 略有不同且不兼容。
最好将该 shell 称为 API,而ksh93不是仅仅ksh指ksh几ksh88十年来的 API(除了 ksh93 实现之外的所有实现)。ksh93直到 2000 年之后几年,其代码作为开源发布,才得到广泛使用。
ksh93typeset仅执行静态作用域。POSIX 反对指定 ksh88 的typeset/ local,因为它是动态作用域(尽管大多数语言(如 C)执行静态作用域,但动态作用域在 shell 中更自然地出现,因为它是通过子 shell 或环境实现的效果),这解释了为什么 ksh93 被重写以进行静态应对。
后来,大多数其他 shell 都实现了 ksh88 的作用域,因此 ksh93 现在是例外。现在讽刺的是,POSIX 唯一合理的选择是指定动态范围。虽然 David Korn 最初拒绝在 ksh93 中实现动态作用域,但他曾表示可以使用localPOSIX 邮件列表上的关键字/builtin 来考虑它,但在这一切完全实现之前他已经退休了。
AT&T 的 ksh93v- beta 和最终版本可以使用实验性“bash”模式(实际上默认启用)进行编译,该模式在作为 调用时执行动态作用域(以函数形式,包括 withlocal和typeset)。默认情况下,在 ksh2020 中该模式将被禁用,但即使未编译 bash 模式(尽管仍具有静态作用域), /别名仍将被保留。ksh93bashbashlocaldeclaretypeset
现在,如果我们将 beta 版本及其 bash 模式放在一边,ksh93仅执行静态作用域,并且仅在使用 ksh 样式语法 ( ) 声明的函数中执行function name { code; }。您会感到困惑,因为使用 Bourne 风格语法 () 声明的函数根本f() command不执行作用域。这与使用 . 调用的源文件或 ksh 函数相同。在这些中,不会在函数的作用域中声明新变量(该函数没有作用域),它只是更新当前作用域中变量的类型,该变量的类型将是全局作用域或 ksh- 的作用域style 函数,如果 Bourne 风格(最终)是从 ksh 风格函数中调用的。. name [args]typeset
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"
Run Code Online (Sandbox Code Playgroud)
给出:
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
Run Code Online (Sandbox Code Playgroud)
在 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
Run Code Online (Sandbox Code Playgroud)
然后将您的函数声明为:
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"
Run Code Online (Sandbox Code Playgroud)
(并且仅local在使用 声明的函数中使用fn_with_local_scope)。
这使
internal IFS = 123
descended IFS = 123
external IFS = abc
Run Code Online (Sandbox Code Playgroud)
在所有 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"
Run Code Online (Sandbox Code Playgroud)
现在,如果您要使用 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"
}
Run Code Online (Sandbox Code Playgroud)
在我看来,这是一个更好的设计,并且该代码在执行动态作用域的 shell 中也可以正常工作(您仍然需要解决不同的函数定义语法)。
进一步阅读: