支持用于定义局部变量的 `local` 关键字的 shell 列表

mja*_*mja 11 shell shell-script

我知道 Bash 和 Zsh 支持local变量,但有些系统只有 POSIX 兼容的 shell。并且local在 POSIX shell 中未定义。

所以我想问一下哪些shell支持local关键字定义局部变量?

编辑:关于外壳我的意思是默认/bin/sh外壳。

Sté*_*las 25

不是支持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
  ...
}
Run Code Online (Sandbox Code Playgroud)

(后来在 Bourne shell 中添加了函数支持,但使用了不同的语法 ( f() command) 并且后来也添加了对该语法的ksh支持;Bourne shell 从来没有本地作用域(当然通过子 shell 除外))

local内置AFAIK首次加入Almquist外壳在1989年(在BSD系统,仪表板,busybox的SH使用),但显著不同于工作kshtypesetash衍生品不支持typeset作为 的别名local,但您始终可以手动定义一个。

bash 和 zsh分别在 1989 年和 1991 年添加了typeset别名local

ksh88local作为一个未公开的别名添加到typeset大约 1990 年和 pdksh 及其衍生产品于 1994 年。posh(基于pdksh)已删除typeset(严格遵守 Debian 政策,要求local,但不要求typeset)。

POSIX 最初反对指定typeset它是动态范围。所以 ksh93(1993 年 David Korn 对 ksh 的重写)转而使用静态作用域。同样在 ksh93 中,与 ksh88 不同,局部作用域仅对使用ksh语法 ( function f {...}) 而非 Bourne 语法 ( f() {...})声明的函数进行,并且local别名已被删除。

然而从AT&T的ksh93v-β和最终版本可与进行动态范围界定实验“的bash”模式(通过默认启用实际)(以麻烦的功能形式,包括与被编译localtypeset)时ksh93被调用作为bashlocaltypeset这种情况的不同之处在于它只能从函数内部调用。bash模式将在默认情况下ksh2020被禁用,虽然local/declare别名typeset,即使在未编译bash的模式将被保留(静态作用域虽仍)。

yash(写得很晚),有typeset(à la ksh88),但local自版本 2.48(2018 年 12 月)以来只有它的别名。

@Schily维护了一个 Bourne shell 后代,它最近主要符合 POSIX,称为bosh自 2016-07-06 版本以来支持本地范围(带有local,类似于ash)。

因此,如今具有某种形式的变量局部作用域的 Bourne-like shell 是:

  • ksh,所有实现及其衍生产品(ksh88、ksh93、pdksh 和衍生产品,如 posh、mksh、OpenBSD sh)。
  • ash 及其所有衍生物(NetBSD sh、FreeBSD sh、dash、busybox sh)
  • 猛击
  • zsh
  • 亚什
  • 波什

sh不同系统而言,请注意有些系统(大多数)POSIXsh/bin其中,而其他系统则不在(例如 Solaris 中/usr/xpg4/bin)。对于sh在各种系统上的实施,我们有:

  • ksh88:大多数 SysV 衍生的商业 Unices(AIX、HP/UX、Solaris¹...)
  • bash:大多数 GNU/Linux 系统、Cygwin、macOS
  • ash:默认在 Debian 和大多数衍生产品(包括 Ubuntu、Linux/Mint)上,但管理员可以将其更改为 bash 或 mksh。NetBSD、FreeBSD 和它们的一些衍生产品(不是 macOS)。
  • busybox sh:许多(如果不是大多数)嵌入式 Linux 系统
  • pdksh 或衍生产品:OpenBSD、MirOS

现在,它们的不同之处:

  • typeset(ksh、pdksh、bash、zsh、localyash )与(ksh88、pdksh、bash、zsh、ash、yash 2.48+)。
  • 静态(ksh93,在function f {...}函数中),vs 动态范围(所有其他 shell)。例如,无论是function f { typeset v=1; g; echo "$v"; }; function g { v=2; }; f输出1还是2. 另请参阅该export属性如何影响 中的作用域ksh93
  • 无论local/typeset只是使变量地方ashbosh),或创建变量(其他shell)的新实例。例如,是否v=1; f() { local v; echo "${v:-empty}"; }; f输出1empty(另请参阅localvar_inheritbash 5.0 及更高版本中的选项)。
  • 对于那些创建新变量的变量,新变量是否继承了属性(如export)和/或类型,以及哪些继承自父作用域中的变量。举例来说,无论是export V=1; f() { local V=2; printenv V; }; f打印12或没有。
  • 该新变量是否具有初始值(空,0,空列表,取决于类型,zsh)或最初未设置。
  • 无论是unset V在局部范围的叶子变量的变量unset,或者只是剥离范围界定的一个水平(mkshyashbash在某些情况下)。例如,是否v=1; f() { local v=2; unset v; echo "$v"; }输出1或不输出(另请参阅localvar_unsetbash 5.0 及更高版本中的选项)
  • 像 for export,无论它是关键字还是仅仅是内置的或两者兼而有之,以及在什么条件下它被视为关键字。
  • 像 for export,参数是被解析为普通命令参数还是赋值(以及在什么条件下)。
  • 是否可以声明local一个在父作用域中只读的变量。
  • v=value myfunctionwheremyfunction本身声明v为本地与否的交互。

我现在想到的就是这些。检查上面的奥斯汀组错误以获取更多详细信息。

至于外壳选项的局部范围(与变量相反),支持它的外壳是:

  • ksh88 (具有两种函数定义语法):默认情况下完成,我不知道有什么方法可以禁用它。
  • ash(自 1989 年起):使用local -. 它使$-参数(存储选项列表)本地化。
  • ksh93: 现在只为function f {...}函数做。
  • zsh(自 1995 年以来)。与setopt localoptions. 还emulate -L用于将仿真模式(及其选项集)设为函数的本地。
  • bash(自2016)与local -ash,但仅限于所管理的选项set,而不是由管理的那些shopt

¹ shSolaris 上的 POSIX是/usr/xpg4/bin/sh(尽管它有许多一致性错误,包括函数本地的那些选项)。/bin/shSolaris 10 之前是 Bourne shell(所以没有本地作用域),因为 Solaris 11 是 ksh93


gle*_*man 7

为了跟进 St\xc3\xa9phane\'s 答案中的提示,使用 subshel​​ls 可以获得局部效果。我无法访问真正的 POSIX shell,但这在 busybox 中有效ash——用()括号而不是{}大括号声明你的函数。这会强制该函数在子 shell 中运行。

\n\n
func() (\n    echo "in func, before declaring: x=$x"\n    x=10\n    echo "in func, after declaring: x=$x"\n)\n\nx=5\necho "before func: x=$x"\nfunc\necho "after func: x=$x"\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出

\n\n
before func: x=5\nin func, before declaring: x=5\nin func, after declaring: x=10\nafter func: x=5\n
Run Code Online (Sandbox Code Playgroud)\n\n

这表明该函数可以访问全局变量,并且在函数中设置变量不会改变全局变量。当我想要更改$IFScd更改目录时,我有时会使用这种技术,但我不希望这些操作影响程序的其余部分。

\n

  • 我认为在早期的 UNIX 时代,如果您确实需要的话,这是获取局部变量和任何其他局部状态的预期方法,这就是为什么早期的 Bourne shell 没有它们。分叉一个新进程是一种简单、通用、可组合的方式来隔离_all_状态,并且 shell 似乎旨在作为用于调用进程并将这些进程与文件描述符重定向粘合在一起的 DSL,而不是作为成熟的通用目的语言将执行足够多的实质性逻辑来需要局部变量。 (3认同)