我知道 Bash 和 Zsh 支持local
变量,但有些系统只有 POSIX 兼容的 shell。并且local
在 POSIX shell 中未定义。
所以我想问一下哪些shell支持local
关键字定义局部变量?
编辑:关于 shell 我指的是默认/bin/sh
shell。
答案1
这不是支持与local
不支持那么简单。具有一种或另一种本地作用域形式的 shell 之间的语法及其执行方式有很多变化。
这就是为什么很难提出一个所有人都同意的标准。看http://austingroupbugs.net/bug_view_page.php?bug_id=767感谢 POSIX 在这方面所做的努力。
本地作用域首先在 80 年代初添加到 ksh 中。
在函数中声明局部变量的语法是typeset
:
function f {
typeset var=value
set -o noglob # also local to the function
...
}
(后来在 Bourne shell 中添加了函数支持,但使用了不同的语法 ( f() command
),并且ksh
后来也添加了对该语法的支持;Bourne shell 从未具有本地作用域(当然通过子 shell 除外))
内置的AFAIKlocal
于 1989 年首先添加到 Almquist shell(用于 BSD、dash、busybox sh),但工作方式与ksh
的显着不同typeset
。ash
衍生品不支持typeset
作为 的别名local
,但您始终可以手动定义一个。
bash 和 zsh分别于 1989 年和 1991 年添加了typeset
别名。local
ksh88 在 1990 年左右添加local
为未记录的别名typeset
,在 1994 年添加为 pdksh 及其衍生物。posh
(基于pdksh
)已删除typeset
(为了严格遵守需要 的 Debian 政策local
,但不需要typeset
)。
POSIX 最初反对指定typeset
理由是它是动态的范围界定。因此 ksh93(David Korn 在 1993 年重写了 ksh)改为静止的相反,范围界定。同样,在 ksh93 中,与 ksh88 不同,局部作用域仅针对使用ksh
语法 ( function f {...}
) 声明的函数完成,而不是 Bourne 语法 ( f() {...}
),并且local
别名已被删除。
然而,来自 AT&T 的 ksh93v- beta 和最终版本可以使用实验性“bash”模式(实际上默认启用)进行编译,该模式在作为 调用时执行动态作用域(以函数形式,包括 withlocal
和typeset
)。 与这种情况的不同之处在于它只能从函数内部调用。ksh93
bash
local
typeset
那bash
ksh2020 中默认禁用尽管local
/别名declare
typeset
即使未编译 bash 模式,(尽管仍然具有静态范围)。
yash
(写得很晚),有typeset
(à la ksh88),但local
自版本 2.48(2018 年 12 月)以来仅作为它的别名。
@希利(谁可悲的是于2021年去世)用于维护 Bourne shell 后代,最近已基本符合 POSIX 标准,称为bosh
自 2016-07-06 版本起支持本地范围(与local
类似ash
)。
因此,如今具有某种形式的局部变量作用域的类似 Bourne 的 shell 是:
- ksh、所有实现及其衍生产品(ksh88、ksh93、pdksh 以及 posh、mksh、OpenBSD sh 等衍生产品)。
- ash 及其所有衍生产品(NetBSD sh、FreeBSD sh、dash、busybox sh)
- 巴什
- 桀骜
- 亚什
- 波什
就不同系统而言,请注意,有些系统(大多数)包含sh
POSIX ,而另一些系统则不包含(例如 Solaris )。为了在各种系统上实施,我们有:sh
/bin
/usr/xpg4/bin
sh
- ksh88:大多数 SysV 衍生的商业 Unices(AIX、HP/UX、Solaris1...)
- bash:大多数 GNU/Linux 系统、Cygwin、macOS
- ash:默认情况下,在 Debian 和大多数衍生产品(包括 Ubuntu、Linux/Mint)上,管理员可以将其更改为 bash 或 mksh。 NetBSD、FreeBSD 及其一些衍生产品(不包括 macOS)。
- busybox sh:许多(如果不是大多数)嵌入式 Linux 系统
- pdksh 或衍生产品:OpenBSD、MirBSD、Android
现在,他们的不同之处:
typeset
(ksh、pdksh、bash、zsh、yash)与local
(ksh88、pdksh、bash、zsh、ash、yash 2.48+)。- 支持的选项列表。
- 静态(ksh93,在
function f {...}
函数中)与动态作用域(所有其他 shell)。例如,无论是function f { typeset v=1; g; echo "$v"; }; function g { v=2; }; f
输出1
还是2
.另请参阅该export
属性如何影响ksh93
. - 无论
local
/typeset
只是创建变量当地的(ash
,bosh
),或创建变量的新实例(其他 shell)。例如,是否v=1; f() { local v; echo "${v:-empty}"; }; f
输出1
或empty
(另请参阅localvar_inherit
bash 5.0 及更高版本中的选项)。 - 对于创建新变量的变量,新变量是否继承属性(如
export
)和/或类型以及哪些来自父作用域中的变量。例如,是否export V=1; f() { local V=2; printenv V; }; f
打印1
,2
或什么也不打印。 - 该新变量是否具有初始值(空、0、空列表,取决于类型
zsh
)或最初未设置。 - 是否
unset V
在局部作用域中的变量上留下该变量unset
,或者只是果皮一级范围(在某些情况下mksh
,yash
,bash
)。例如,是否v=1; f() { local v=2; unset v; echo "$v"; }
输出1
或不输出(另请参阅localvar_unset
bash 5.0 及更高版本中的选项) - 就像 for 一样
export
,它是一个关键字还是只是一个内置关键字,或者两者兼而有之,以及在什么条件下它被视为关键字。 - 与 for 一样
export
,参数是否被解析为普通命令参数或赋值(以及在什么条件下)。 - 您是否可以声明当地的在父作用域中只读的变量。
v=value myfunction
与where本身的交互myfunction
声明v
为本地或非本地。
我现在想到的就是这些。检查上面的奥斯汀组错误以获取更多详细信息。
至于 shell 的本地作用域选项(相对于变量),支持它的 shell 是:
ksh88
(使用两个函数定义语法):默认完成,我不知道有什么方法可以禁用它。ash
(自 1989 年起):与local -
.它将$-
参数(存储选项列表)设置为本地参数。ksh93
function f {...}
:现在仅针对功能完成。zsh
(自 1995 年起)。和setopt localoptions
。还可以emulate -L
将仿真模式(及其选项集)设置为函数本地化。bash
(自 2016 年起)与local -
类似ash
,但仅适用于 管理的选项set
,而不是 管理的选项shopt
。
sh
1 Solaris 上的POSIX是/usr/xpg4/bin/sh
(尽管它有许多一致性错误,包括那些函数本地选项)。/bin/sh
Solaris 10 之前是 Bourne shell(因此没有本地作用域),自 Solaris 11 开始是 ksh93
答案2
为了跟进 Stéphane 答案中的提示,使用子 shell 可以获得本地影响。我无法访问真正的 POSIX shell,但这在 busybox 中有效——用括号而不是大括号ash
声明你的函数。这会强制该函数在子 shell 中运行。()
{}
func() (
echo "in func, before declaring: x=$x"
x=10
echo "in func, after declaring: x=$x"
)
x=5
echo "before func: x=$x"
func
echo "after func: x=$x"
输出
before func: x=5
in func, before declaring: x=5
in func, after declaring: x=10
after func: x=5
这表明该函数可以访问全局变量,并且在函数中设置变量不会改变全局变量。当我想要更改$IFS
或更改目录时,我有时会使用这种技术cd
,但我不希望这些操作影响程序的其余部分。