Is there a POSIX Compliant way to limit the scope of a variable to the function it is declared in? i.e.:
Testing() { TEST="testing" } Testing echo "Test is: $TEST"
should print "Test is:". I've read about the declare, local, and typeset keywords, but it doesn't look like they are required POSIX built-ins.
POSIX is an acronym for “Portable Operating System Interface”. POSIX shell is based on the standard defined in Portable Operating System Interface (POSIX) – IEEE P1003. 2. It is a set of standards codified by the IEEE and issued by ANSI and ISO. POSIX makes task of cross-platform software development easy.
POSIX Shell is a command line shell for computer operating system which was introduced by IEEE Computer Society. POSIX stands for Portable Operating System Interface. POSIX Shell is based on the standard defined in Portable Operating System Interface (POSIX) – IEEE P1003.
Variable Scope of Bash FunctionsBy default, every variable has a global scope that means it is visible everywhere in the script. You can also create a variable as a local variable. When you declare a local variable within the function body, it is only visible within that function.
It is normally done with the local
keyword, which is, as you seem to know, not defined by POSIX. Here is an informative discussion about adding 'local' to POSIX.
However, even the most primitive POSIX-compliant shell I know of which is used by some GNU/Linux distributions as the /bin/sh
default, dash
(Debian Almquist Shell), supports it. FreeBSD and NetBSD use ash
, the original Almquist Shell, which also supports it. OpenBSD uses a ksh
implementation for /bin/sh
which also supports it. So unless you're aiming to support non-GNU non-BSD systems like Solaris, or those using standard ksh, etc., you could get away with using local
. (Might want to put some comment right at the start of the script, below the shebang line, noting that it is not strictly a POSIX sh script. Just to be not evil.) Having said all that, you might want to check the respective man-pages of all these sh
implementations that support local
, since they might have subtle differences in how exactly they work. Or just don't use local
:
If you really want to conform fully to POSIX, or don't want to mess with possible issues, and thus not use local
, then you have a couple options. The answer given by Lars Brinkhoff is sound, you can just wrap the function in a sub-shell. This might have other undesired effects though. By the way shell grammar (per POSIX) allows the following:
my_function() ( # Already in a sub-shell here, # I'm using ( and ) for the function's body and not { and }. )
Although maybe avoid that to be super-portable, some old Bourne shells can be even non-POSIX-compliant. Just wanted to mention that POSIX allows it.
Another option would be to unset
variables at the end of your function bodies, but that's not going to restore the old value of course so isn't really what you want I guess, it will merely prevent the variable's in-function value to leak outside. Not very useful I guess.
One last, and crazy, idea I can think of is to implement local
yourself. The shell has eval
, which, however evil, yields way to some insane possibilities. The following basically implements dynamic scoping a la old Lisps, I'll use the keyword let
instead of local
for further cool-points, although you have to use the so-called unlet
at the end:
# If you want you can add some error-checking and what-not to this. At present, # wrong usage (e.g. passing a string with whitespace in it to `let', not # balancing `let' and `unlet' calls for a variable, etc.) will probably yield # very very confusing error messages or breakage. It's also very dirty code, I # just wrote it down pretty much at one go. Could clean up. let() { dynvar_name=$1; dynvar_value=$2; dynvar_count_var=${dynvar_name}_dynvar_count if [ "$(eval echo $dynvar_count_var)" ] then eval $dynvar_count_var='$(( $'$dynvar_count_var' + 1 ))' else eval $dynvar_count_var=0 fi eval dynvar_oldval_var=${dynvar_name}_oldval_'$'$dynvar_count_var eval $dynvar_oldval_var='$'$dynvar_name eval $dynvar_name='$'dynvar_value } unlet() for dynvar_name do dynvar_count_var=${dynvar_name}_dynvar_count eval dynvar_oldval_var=${dynvar_name}_oldval_'$'$dynvar_count_var eval $dynvar_name='$'$dynvar_oldval_var eval unset $dynvar_oldval_var eval $dynvar_count_var='$(( $'$dynvar_count_var' - 1 ))' done
Now you can:
$ let foobar test_value_1 $ echo $foobar test_value_1 $ let foobar test_value_2 $ echo $foobar test_value_2 $ let foobar test_value_3 $ echo $foobar test_value_3 $ unlet foobar $ echo $foobar test_value_2 $ unlet foobar $ echo $foobar test_value_1
(By the way unlet
can be given any number of variables at once (as different arguments), for convenience, not showcased above.)
Don't try this at home, don't show it to children, don't show it your co-workers, don't show it to #bash
at Freenode, don't show it to members of the POSIX committee, don't show it to Mr. Bourne, maybe show it to father McCarthy's ghost to give him a laugh. You have been warned, and you didn't learn it from me.
EDIT:
Apparently I've been beaten, sending the IRC bot greybot
on Freenode (belongs to #bash
) the command "posixlocal" will make it give one some obscure code that demonstrates a way to achieve local variables in POSIX sh. Here is a somewhat cleaned up version, because the original was difficult to decipher:
f() { if [ "$_called_f" ] then x=test1 y=test2 echo $x $y else _called_f=X x= y= command eval '{ typeset +x x y; } 2>/dev/null; f "$@"' fi }
This transcript demonstrates usage:
$ x=a $ y=b $ f test1 test2 $ echo $x $y a b
So it lets one use the variables x
and y
as locals in the then
branch of the if form. More variables can be added at the else
branch; note that one must add them twice, once like variable=
in the initial list, and once passed as an argument to typeset
. Note that no unlet
or so is needed (it's a "transparent" implementation), and no name-mangling and excessive eval
is done. So it seems to be a much cleaner implementation overall.
EDIT 2:
Comes out typeset
is not defined by POSIX, and implementations of the Almquist Shell (FreeBSD, NetBSD, Debian) don't support it. So the above hack will not work on those platforms.
I believe the closest thing would be to put the function body inside a subshell.
E.g. try this
foo() { ( x=43 ; echo $x ) } x=42 echo $x foo echo $x
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With