Note ksh88 and all its clones do dynamic scoping and have been supporting local
for it since at least 1990 for ksh88 and 1994 for pdksh
(and 1989 for bash
which (now) implements a lot of the ksh88 API).
The ksh
you're referring to is ksh93
, a newer implementation from scratch by David Korn with a slightly different and incompatible API.
It's a good idea to refer to that shell as ksh93
instead of just ksh
as ksh
alone has meant the ksh88
API for a few decades (the one all but the ksh93 implementations implement). ksh93
has not been in wide use until a few years after 2000 when its code was released as open source.
ksh93's typeset
only does static scoping. POSIX had objected to specifying ksh88's typeset
/local
on the ground that it was dynamic scoping (though, even though most languages like C do static scoping, dynamic scoping comes more naturally in shells as it's what you get in effect with subshells or with the environment), which explains why ksh93 was rewritten to do static coping.
Later, most other shells implemented scoping a la ksh88, so ksh93 is now the odd one out. And now ironically, POSIX's only reasonable option would be to specify a dynamic scope. While David Korn initially refused to implement dynamic scoping in ksh93, he had said he could consider it with the local
keyword/builtin on the POSIX mailing list, but he has retired before that fully happened.
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
. 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).
Now if we leave that beta release aside and its bash mode, ksh93 only does static scoping and only in the functions declared with the ksh-style syntax (function name { code; }
). You're getting confused because the functions declared with the Bourne-style syntax (f() command
) don't do scoping at all. It's the same with sourced files or ksh functions invoked with . name [args]
. In those, typeset
doesn't declare a new variable in the function's scope (that function has no scope), it just updates the type of the variable in the current scope which is going to be either the global scope or the scope of a ksh-style function if that Bourne-style was (eventually) called from within a ksh-style function.
The code of Bourne-style functions is run as if embedded/copy-pasted/sourced wherever they're invoked.
var=global
function ksh_function {
typeset var=private
echo "ksh1: $var"
bourne_function
echo "ksh2: $var"
other_ksh_function other
echo "ksh3: $var"
. other_ksh_function other_invoked_with_dot
echo "ksh4: $var"
}
bourne_function() {
typeset var=set-from-bourne-function
}
function other_ksh_function {
echo "other: $var"
var=$1
}
ksh_function
echo "global: $var"
Gives:
ksh1: private
ksh2: set-from-bourne-function
other: global
ksh3: set-from-bourne-function
other: set-from-bourne-function
ksh4: other_invoked_with_dot
global: other
It's not possible in ksh93 to have dynamic scoping other than by using subshells or implementing a variable stack by yourself like in that locvar
proof of concept or you export the variable for it to be passed to every command (including functions declared with the ksh-style functions, including external commands via the environment).
In your particular case where only the testlocal
function (and not testdescend
) need a local scope, you can use the shdef
+kshdef
approach as described there or do something like:
case $KSH_VERSION in
(*" 93"*)
fn_with_local_scope() {
alias local=typeset
eval "function $1 {
$(cat)
}"
}
;;
(*)
fn_with_local_scope() {
eval "$1() {
$(cat)
}"
}
;;
esac
And then declare your functions as:
fn_with_local_scope testlocal << '}'
local IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
testdescend(){
echo "descended IFS = $IFS"
}
IFS=abc
testlocal
echo "external IFS = $IFS"
(and only use local
in functions declared with fn_with_local_scope
).
Which gives
internal IFS = 123
descended IFS = 123
external IFS = abc
in all shells (note that you need a recent version of yash
(2.48 or above) for it to support local
).
Or if you're OK for the local variables to also be exported (in ksh93 only):
case $KSH_VERSION in
(*" 93"*)
fn() {
alias local='typeset -x'
eval "function $1 {
$(cat)
}"
}
;;
(*)
fn() {
eval "$1() {
$(cat)
}"
}
;;
esac
fn testlocal << '}'
local IFS
IFS=123
echo "internal IFS = $IFS"
testdescend
}
fn testdescend << '}'
echo "descended IFS = $IFS"
}
IFS=abc
testlocal
echo "external IFS = $IFS"
Now, if you were to do something like that in a language with static scoping like C or ksh93, you'd do something like:
function testlocal {
typeset IFS
IFS=123
echo "internal IFS = $IFS"
testdescend "$IFS"
}
function testdescend {
typeset IFS="$1" # explicitly get the value $IFS from the caller
echo "descended IFS = $IFS"
}
Which seems to me like a better design, and that code would work OK as well in shells that do dynamic scoping (you'd still need to address the different function definition syntax).
Further reading:
#!/usr/bin/env bash
to the top of the file and you're good. Seriously. Trying to write actually portable shell scripts is an exercise in pain and futility, IMO, and maintainers are going to absolutely hate the result. Or you could./do-the-thing-in-most-shells.sh || ./do-the-thing-in-ksh.ksh
. – l0b0 Sep 08 '19 at 18:59typeset
/local
and ksh93 has static scoping only and withfunction f {...;}
only and withtypeset
only.f() {...;}
functions have no local scope at all there. – Stéphane Chazelas Sep 10 '19 at 05:37local
? : Most of them except ksh93. (2) I am using ksh93. (3) Dynamic vs. Static in ksh93 ? Care to explain why Try it online! prints14 14 13 12
Is it only Static ?. The ksh93 has both scopes available. (4) You propose a complex workaround to have the function only appear in ksh ? ... @StéphaneChazelas – Sep 10 '19 at 22:30local
. @StéphaneChazelas – Sep 10 '19 at 23:22bash script
– Jonas Berlin Sep 11 '19 at 07:57