Skip to main content
2 of 21
added 655 characters in body
Stéphane Chazelas
  • 584.6k
  • 96
  • 1.1k
  • 1.7k

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 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.

pdksh and its derivatives added local as an alias to typeset in 1994. posh 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 1993 was rewritten to do 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() {...}).

ksh93 still doesn't have local.

yash (written much later), has typeset (a la ksh88), but not local.

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, mksk, OpenBSD sh).
  • ash and all its derivatives (NetBSD sh, FreeBSD sh, dash, busybox sh)
  • bash
  • zsh
  • yash

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, MirOS

Now, where they differ:

  • typeset (ksh, pdksh, bash, zsh, yash) vs local (pdksh, bash, zsh, ash).
  • 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.
  • whether local/typeset just makes the variable local (ash), 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.
  • 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.
  • 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.

Stéphane Chazelas
  • 584.6k
  • 96
  • 1.1k
  • 1.7k