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.
pdksh and its derivatives added `local` as an alias to `typeset` 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() {...}`).
ksh93 still doesn't have `local`.
`yash` (written much later), has `typeset` (a la ksh88), but not `local`.
[@Schily](/users/120884/schily) maintains a Bourne shell descendant which has been recently made mostly POSIX compliant, called [`bosh`](http://schillix.sourceforge.net/man/man1/bosh.1.html) 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
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`, `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).
- 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.
- like for `export`, whether the arguments are parsed as normal command arguments or as assignments (and under what condition).
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`.
---
<sup>¹ 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</sup>