Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>It is normally done with the <code>local</code> keyword, which is, as you seem to know, not defined by POSIX. Here is an informative <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=381091" rel="noreferrer">discussion about adding 'local' to POSIX</a>.</p> <p>However, even the most primitive POSIX-compliant shell I know of which is used by some GNU/Linux distributions as the <code>/bin/sh</code> default, <code>dash</code> (Debian Almquist Shell), supports it. FreeBSD and NetBSD use <code>ash</code>, the original Almquist Shell, which also supports it. OpenBSD uses a <code>ksh</code> implementation for <code>/bin/sh</code> 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 <code>local</code>. (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 <code>sh</code> implementations that support <code>local</code>, since they might have subtle differences in how exactly they work. Or just don't use <code>local</code>:</p> <p>If you really want to conform fully to POSIX, or don't want to mess with possible issues, and thus not use <code>local</code>, 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:</p> <pre><code>my_function() ( # Already in a sub-shell here, # I'm using ( and ) for the function's body and not { and }. ) </code></pre> <p>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.</p> <p>Another option would be to <code>unset</code> variables at the end of your function bodies, but that's <em>not</em> 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.</p> <p>One last, and crazy, idea I can think of is to implement <code>local</code> yourself. The shell has <code>eval</code>, which, however evil, yields way to some insane possibilities. The following basically implements dynamic scoping a la old Lisps, I'll use the keyword <code>let</code> instead of <code>local</code> for further cool-points, although you have to use the so-called <code>unlet</code> at the end:</p> <pre><code># 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 </code></pre> <p>Now you can:</p> <pre><code>$ 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 </code></pre> <p>(By the way <code>unlet</code> can be given any number of variables at once (as different arguments), for convenience, not showcased above.)</p> <p>Don't try this at home, don't show it to children, don't show it your co-workers, don't show it to <code>#bash</code> 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.</p> <p><strong>EDIT:</strong></p> <p>Apparently I've been beaten, sending the IRC bot <code>greybot</code> on Freenode (belongs to <code>#bash</code>) 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:</p> <pre><code>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&gt;/dev/null; f "$@"' fi } </code></pre> <p>This transcript demonstrates usage:</p> <pre><code>$ x=a $ y=b $ f test1 test2 $ echo $x $y a b </code></pre> <p>So it lets one use the variables <code>x</code> and <code>y</code> as locals in the <code>then</code> branch of the if form. More variables can be added at the <code>else</code> branch; note that one must add them twice, once like <code>variable=</code> in the initial list, and once passed as an argument to <code>typeset</code>. Note that no <code>unlet</code> or so is needed (it's a "transparent" implementation), and no name-mangling and excessive <code>eval</code> is done. So it seems to be a much cleaner implementation overall.</p> <p><strong>EDIT 2:</strong></p> <p>Comes out <code>typeset</code> 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. </p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload