It's not as simple as supporting local
or not. There is a lot of variation on the syntax and how it's done between shells that have one form or other of local scope.
That's why it's very hard to come up with a standard that agrees with all. See http://austingroupbugs.net/bug_view_page.php?bug_id=767 for the POSIX effort on that front.
local scope was added first in ksh in the early 80s.
The syntax to declare a local variable in a function was with typeset
:
function f {
typeset var=value
set -o noglob # also local to the function
...
}
(function support was added to the Bourne shell later, but with a different syntax (f() command
) and ksh
added support for that one as well later; the Bourne shell never had local scope (except of course via subshells))
The local
builtin AFAIK was added first to the Almquist shell (used in BSDs, dash, busybox sh) in 1989, but works significantly differently from ksh
's typeset
. ash
derivatives don't support typeset
as an alias to local
, but you can always define one by hand.
bash and zsh added typeset
aliased to local
in 1989 and 1991 respectively.
ksh88 added local
as an undocumented alias to typeset
circa 1990 and pdksh and its derivatives in 1994. posh
(based on pdksh
) removed typeset
(for strict compliance to the Debian Policy that requires local
, but not typeset
).
POSIX initially objected to specifying typeset
on the ground that it was dynamic scoping. So ksh93 (a rewrite of ksh in 1993 by David Korn) switched to static scoping instead. Also in ksh93, as opposed to ksh88, local scoping is only done for functions declared with the ksh
syntax (function f {...}
), not the Bourne syntax (f() {...}
) and the local
alias was removed.
However the ksh93v- beta and final version from AT&T can be compiled with an experimental "bash" mode (actually enabled by default) that does dynamic scoping (in bother forms of functions, including with local
and typeset
) when ksh93
is invoked as bash
. local
differs from typeset
in that case in that it can only be invoked from within a function. That bash
mode will be disabled by default in ksh2020 though the local
/declare
aliases to typeset
will be retained even when the bash mode is not compiled in (though still with static scoping).
yash
(written much later), has typeset
(à la ksh88), but has only had local
as an alias to it since version 2.48 (December 2018).
@Schily (who sadly passed away in 2021) used to maintain a Bourne shell descendant which has been recently made mostly POSIX compliant, called bosh
that supports local scope since version 2016-07-06 (with local
, similar to ash
).
So the Bourne-like shells that have some form of local scope for variables today are:
- ksh, all implementations and their derivatives (ksh88, ksh93, pdksh and derivatives like posh, mksh, OpenBSD sh).
- ash and all its derivatives (NetBSD sh, FreeBSD sh, dash, busybox sh)
- bash
- zsh
- yash
- bosh
As far as the sh
of different systems go, note that there are systems where the POSIX sh
is in /bin
(most), and others where it's not (like Solaris where it's in /usr/xpg4/bin
). For the sh
implementation on various systems we have:
- ksh88: most SysV-derived commercial Unices (AIX, HP/UX, Solaris¹...)
- bash: most GNU/Linux systems, Cygwin, macOS
- ash: by default on Debian and most derivatives (including Ubuntu, Linux/Mint) though can be changed by the admin to bash or mksh. NetBSD, FreeBSD and some of their derivatives (not macOS).
- busybox sh: many if not most embedded Linux systems
- pdksh or derivatives: OpenBSD, MirBSD, Android
Now, where they differ:
typeset
(ksh, pdksh, bash, zsh, yash) vs local
(ksh88, pdksh, bash, zsh, ash, yash 2.48+).
- the list of supported options.
- static (ksh93, in
function f {...}
function), vs dynamic scoping (all other shells). For instance, whether function f { typeset v=1; g; echo "$v"; }; function g { v=2; }; f
outputs 1
or 2
. See also how the export
attribute affects scoping in ksh93
.
- whether
local
/typeset
just makes the variable local (ash
, bosh
), or creates a new instance of the variable (other shells). For instance, whether v=1; f() { local v; echo "${v:-empty}"; }; f
outputs 1
or empty
(see also the localvar_inherit
option in bash 5.0 and above).
- with those that create a new variable, whether the new one inherits the attributes (like
export
) and/or type and which ones from the variable in the parent scope. For instance, whether export V=1; f() { local V=2; printenv V; }; f
prints 1
, 2
or nothing.
- whether that new variable has an initial value (empty, 0, empty list, depending on type,
zsh
) or is initially unset.
- whether
unset V
on a variable in a local scope leaves the variable unset
, or just peels one level of scoping (mksh
, yash
, bash
under some circumstances). For instance, whether v=1; f() { local v=2; unset v; echo "$v"; }
outputs 1
or nothing (see also the localvar_unset
option in bash 5.0 and above)
- like for
export
, whether it's a keyword or only a mere builtin or both and under what condition it's considered as a keyword.
- like for
export
, whether the arguments are parsed as normal command arguments or as assignments (and under what condition).
- whether you can declare local a variable that was readonly in the parent scope.
- the interactions with
v=value myfunction
where myfunction
itself declares v
as local or not.
That's the ones I'm thinking of just now. Check the austin group bug above for more details.
As far as local scoping for shell options (as opposed to variables), shells supporting it are:
ksh88
(with both function definition syntax): done by default, I'm not aware of any way to disable it.
ash
(since 1989): with local -
. It makes the $-
parameter (which stores the list of options) local.
ksh93
: now only done for function f {...}
functions.
zsh
(since 1995). With setopt localoptions
. Also with emulate -L
for the emulation mode (and its set of options) to be made local to the function.
bash
(since 2016) with local -
like in ash
, but only for the options managed by set
, not the ones managed by shopt
.
¹ the POSIX sh
on Solaris is /usr/xpg4/bin/sh
(though it has many conformance bugs including those options local to functions). /bin/sh
up to Solaris 10 was the Bourne shell (so no local scope), and since Solaris 11 is ksh93
typeset
instead oflocal
local
keyword; it does support local variables, but viatypeset
not via thelocal
, and only in funcs defined asfunction foo { }
, not in funcs defined asfoo(){ }
.typeset
not working in functions defined asfoo(){ ... }
? Example:foo(){ typeset v=3; }; function bar { typeset v=7; }; v=1; foo; echo $v; bar; echo $v;
=> 3, 3.function foo {...}
to get locals usingtypeset
, but in ksh93 you do. That's bad news, if you used thefoo() {...}
syntax, for shell scripts written for ksh88 (AIX) and then ported to ksh93 (Linux),