POSIX-Compliant Way to Scope Variables to a Function in a Shell Script
Asked Answered
A

9

75

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.

Apollus answered 3/9, 2013 at 17:0 Comment(2)
you can use positional parameters, which are local to the current function, use set -- "$@" value1 value2 at the begining of the function to append values to the parameter list. But there is no easy way to change the individual values, you have always to specify all parameters, like: set -- "$1" "$2" ... valuex ...Rehearsal
see discussion at unix.stackexchange.com/questions/493729/…Morgun
C
94

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.

Crew answered 3/9, 2013 at 20:28 Comment(9)
Wow thorough answer! I'm sure the local keyword will be fine for my purposes, I'm fairly new to bash scripting and just trying to learn things the right way. There's so much history and so many shell variants, it's overwhelming. Running the function in a sub-shell might work for me, but several of my functions call commands and the caller expects the $? variable to be set correctly to determine the result, using a sub-shell might break those functions...??Apollus
1) Decide if you want to use Bash or POSIX sh or what exactly. (Best would be to use a proper programming language of course.) 2) The sub-shell should relay its $? just fine to the parent.Crew
Just a pedantic quibble, the let/unlet solution constructs a variable name to hold the value of the global while the local is in use, but doesn't ensure that that name is not already in use as a global. Such collisions are unlikely, but one should be aware of the possibility.Mezoff
Speaking as a former #bash regular, I'm impressed. Horrified, in parts, but impressed. :)Nonconformance
A small note: in AT&T ksh (including ksh83 and ksh88), typeset works like local inside a function, but only if the function is declared using the function name syntax instead of the name() syntax.Falcate
I'm glad you included sufficiently dire warnings along with this horrific work. :)Unfriended
The Gmane link works again, but I'm not sure it points at the intended post. The closest suitable post I could find on the Debian bug list was Debian bug #294962 requesting a change of policy to require local support for Debian although it's not POSIX. Was that what you were referencing?Narcosynthesis
@Narcosynthesis – The Gmane link only worked as far as getting me the first email in the thread and it has odd Javascript requirements (when JS is suppressed, it shows different content). It was enough for me to find Debian bug 381091, which is definitely the discussion in question (though it's primarily about "$@" vs "$*"), so I've updated the link in this answer to go to it directly.Fribourg
two weirdest pieces of shell code I have ever seen, thank you. almost like ${dev[${dev='dev[1>(${dev[dev]})]'}]}Philodendron
B
13

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
Brenna answered 3/9, 2013 at 18:38 Comment(0)
O
9

This is actually built into the design of POSIX function declarations.

If you would like a variable declared in the parent scope, to be accessible in a function, but leave its value in the parent scope unchanged, simply:

*Declare your function using an explicit subshell, i.e., use a

  • subshell_function() (with parentheses), not

  • inline_function() { with braces ;}


The behavior of inline grouping vs. subshell grouping is consistent throughout the entire language.

If you want to "mix and match", start with an inline function, then nest subshell functions as necessary. It's clunky, but works.

Overrule answered 21/11, 2020 at 18:55 Comment(2)
But what if we want to operate on a mix of local and global variables? This is often needed, and why the local keyword is useful in Bash or other modern shells.Lactometer
Point taken. However, portability is often extremely critical in production environments. Also, you can "mix and match" — see edit.Overrule
N
4

If you'd like to journey down to Hell with me, I've made a more elaborated implementation of the eval concept.

This one automatically keeps an account of your quasi-scoped variables, can be called with a more familiar syntax, and properly unsets (as opposed to merely nulling) variables when leaving nested scopes.


Usage

As you can see, you push_scope to enter a scope, _local to declare your quasi-local variables, and pop_scope to leave a scope. Use _unset to unset a variable, and pop_scope will re-unset it when you back out into that scope again.

your_func() {
    push_scope
    _local x="baby" y="you" z

    x="can"
    y="have"
    z="whatever"
    _unset z

    push_scope
    _local x="you"
    _local y="like"
    pop_scope

    pop_scope
}

Code

All of the gibberish variable name suffixes are to be extra-safe against name collisions.

# Simulate entering of a nested variable scope
# To be used in conjunction with push_scope(), pop_scope(), and _local()
push_scope() {
    SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D=$(( $SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D + 1 ))
}

# Store the present value of the specified variable(s), allowing use in a new scope.
# To be used in conjunction with push_scope(), pop_scope(), and _local()
#
# Parameters:
# $@ : string; name of variable to store the value of
scope_var() {
    for varname_FB94CFD263CF11E89500036F7F345232 in "${@}"; do
        eval "active_varnames_FB94CFD263CF11E89500036F7F345232=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}\""

        # echo "Active varnames: ${active_varnames_FB94CFD263CF11E89500036F7F345232}"

        case " ${active_varnames_FB94CFD263CF11E89500036F7F345232} " in
            *" ${varname_FB94CFD263CF11E89500036F7F345232} "* )
                # This variable was already stored in a previous call
                # in the same scope. Do not store again.
                # echo "Push \${varname_FB94CFD263CF11E89500036F7F345232}, but already stored."
                :
                ;;

            * )
                if eval "[ -n \"\${${varname_FB94CFD263CF11E89500036F7F345232}+x}\" ]"; then
                    # Store the existing value from the previous scope.
                    # Only variables that were set (including set-but-empty) are stored
                    # echo "Pushing value of \$${varname_FB94CFD263CF11E89500036F7F345232}"
                    eval "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_FB94CFD263CF11E89500036F7F345232}=\"\${${varname_FB94CFD263CF11E89500036F7F345232}}\""
                else
                    # Variable is unset. Do not store the value; an unstored
                    # value will be used to indicate its unset state. The
                    # variable name will still be registered.
                    # echo "Not pushing value of \$${varname_FB94CFD263CF11E89500036F7F345232}; was previously unset."
                    :
                fi

                # Add to list of variables managed in this scope.
                # List of variable names is space-delimited.
                eval "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}${varname_FB94CFD263CF11E89500036F7F345232} \""
                ;;
        esac

        unset active_varnames_FB94CFD263CF11E89500036F7F345232
    done

    unset varname_FB94CFD263CF11E89500036F7F345232
}

# Simulate declaration of a local variable
# To be used in conjunction with push_scope(), pop_scope(), and _local()
#
# This function is a convenience wrapper over scope_var().
#
# Can be called just like the local keyword.
# Example usage: _local foo="foofoofoo" bar="barbarbar" qux qaz=""
_local() {
    for varcouple_44D4987063D111E8A46923403DDBE0C7 in "${@}"; do
        # Example string: foo="barbarbar"
        varname_44D4987063D111E8A46923403DDBE0C7="${varcouple_44D4987063D111E8A46923403DDBE0C7%%=*}"
        varvalue_44D4987063D111E8A46923403DDBE0C7="${varcouple_44D4987063D111E8A46923403DDBE0C7#*=}"
        varvalue_44D4987063D111E8A46923403DDBE0C7="${varvalue_44D4987063D111E8A46923403DDBE0C7#${varcouple_44D4987063D111E8A46923403DDBE0C7}}"

        # Store the value for the previous scope.
        scope_var "${varname_44D4987063D111E8A46923403DDBE0C7}"

        # Set the value for this scope.
        eval "${varname_44D4987063D111E8A46923403DDBE0C7}=\"\${varvalue_44D4987063D111E8A46923403DDBE0C7}\""

        unset varname_44D4987063D111E8A46923403DDBE0C7
        unset varvalue_44D4987063D111E8A46923403DDBE0C7
        unset active_varnames_44D4987063D111E8A46923403DDBE0C7
    done

    unset varcouple_44D4987063D111E8A46923403DDBE0C7
}

# Simulate unsetting a local variable.
#
# This function is a convenience wrapper over scope_var().
# 
# Can be called just like the unset keyword.
# Example usage: _unset foo bar qux
_unset() {
    for varname_6E40DA2E63D211E88CE68BFA58FE2BCA in "${@}"; do
        scope_var "${varname_6E40DA2E63D211E88CE68BFA58FE2BCA}"
        unset "${varname_6E40DA2E63D211E88CE68BFA58FE2BCA}"
    done
}

# Simulate exiting out of a nested variable scope
# To be used in conjunction with push_scope(), pop_scope(), and _local()
pop_scope() {
    eval "varnames_2581E94263D011E88919B3D175643B87=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}\""

    # Cannot iterate over $varnames by setting $IFS; $IFS does not work
    # properly on zsh. Workaround using string manipulation.
    while [ -n "${varnames_2581E94263D011E88919B3D175643B87}" ]; do
        # Strip enclosing spaces from $varnames.
        while true; do
            varnames_old_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87}"
            varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87# }"
            varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87% }"

            if [ "${varnames_2581E94263D011E88919B3D175643B87}" = "${varnames_2581E94263D011E88919B3D175643B87}" ]; then
                break
            fi
        done

        # Extract the variable name for the current iteration and delete it from the queue.
        varname_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87%% *}"
        varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87#${varname_2581E94263D011E88919B3D175643B87}}"

        # echo "pop_scope() iteration on \$SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}"
        # echo "varname: ${varname_2581E94263D011E88919B3D175643B87}"

        if eval "[ -n \""\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}+x}"\" ]"; then
            # echo "Value found. Restoring value from previous scope."
            # echo eval "${varname_2581E94263D011E88919B3D175643B87}=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}}\""
            eval "${varname_2581E94263D011E88919B3D175643B87}=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}}\""
            unset "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}"
        else
            # echo "Unsetting \$${varname_2581E94263D011E88919B3D175643B87}"
            unset "${varname_2581E94263D011E88919B3D175643B87}"
        fi

        # Variable cleanup.
        unset varnames_old_2581E94263D011E88919B3D175643B87
    done

    unset SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES
    unset varname_2581E94263D011E88919B3D175643B87
    unset varnames_2581E94263D011E88919B3D175643B87

    SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D=$(( $SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D - 1 ))
}
Newsom answered 30/5, 2018 at 7:23 Comment(0)
S
3

Here is a function that enables scoping:

scope() {
  eval "$(set)" command eval '\"\$@\"'
}

Example script:

x() {
  y='in x'
  echo "$y"
}
y='outside x'
echo "$y"
scope x
echo "$y"

Result:

outside x
in x
outside x
Study answered 25/2, 2017 at 6:12 Comment(1)
Can you comment out your scope function, please?Strain
P
3

Variable Naming Conventions

The OpenBSD developers typically define function-only variables using a leading underscore (examples can be found in /etc/netstart and /etc/rc). If you stick to this convention, then you have two variable scopes: global and function. However, you still have the problem of a function overwriting the variables of other functions.

To take this a step further, you could prefix a function's variables with its name, or an abbreviation of its name. For example:

func1() { _f1_var=v1; echo $_f1_var; }
func2() { _f2_var=v2; echo $_f2_var; }

Here are some conventions one could follow to maintain scoped variables in POSIX-compatible shell scripts:

Variable Description
VAR_NAME environment variables
Var_Name script constants (define using readonly when possible)
var_name global variables
_func_var_name local function variables

Moreover, the author of Portable Shell Programming: An Extensive Collection of Bourne Shell Examples states:

The simplest method to avoid conflicts between variables used inside a function and variables used outside the function is to adopt a naming convention that prevents the conflict. For example, in the shell functions in this book, the name of any variable that is only used within a function begins with the underscore character.

Notice that recursive function calls, while possible, are not very useful because a new set of variables is not created for each instance of the function.

Best Practices

  1. For simple scripts with few functions and a low chance of name collisions, variable scope should not be a concern.

  2. For scripts with many global and function variables, and where functions are mostly called from the script's top level, the underscore-prefix naming convention for function variables is recommended.

  3. If functions call other functions, or a script includes other scripts using the dot command, a variable naming convention at the function-level is recommended.

This is a good question, unfortunately there isn't a good answer.

Phobia answered 14/3, 2023 at 3:20 Comment(0)
A
2

It's possible to simulate local variables in a Posix Shell using a small set of general functions.
The sample code below demonstrates two functions, called Local and EndLocal, which do the trick.

  • Use Local once to declare all local variables at the beginning of a routine.
    Local creates a new scope, and saves the previous definition of each local variable into a new global variable.
  • Use EndLocal before returning from the routine.
    EndLocal restores all previous definitions saved at the current scope, and deletes the last scope.
    Also note that EndLocal preserves the previous exit code.

All functions are short, and use descriptive names, so they should be relatively easy to understand.
They support variables with tricky characters like spaces, single and double quotes.
Caution: They use global variables beginning with LOCAL_, so there's a small risk of collision with existing homonym variables.

The Test routine recursively calls itself 3 times, and modifies a few local and global variables.
The output shows that the A and B local variables are preserved, contrary to the global N variable.

Code:

#!/bin/sh

#-----------------------------------------------------------------------------#
# Manage pseudo-local variables in a Posix Shell

# Check if a variable exists.
VarExists() { # $1=Variable name
  eval "test \"\${${1}+true}\" = \"true\""
}

# Get the value of a variable.
VarValue() { # $1=Variable name
  eval "echo \"\${$1}\""
}

# Escape a string within single quotes, for reparsing by eval
SingleQuote() { # $1=Value
  echo "$1" | sed -e "s/'/'\"'\"'/g" -e "s/.*/'&'/"
}

# Set the value of a variable.
SetVar() { # $1=Variable name; $2=New value
  eval "$1=$(SingleQuote "$2")"
}

# Emulate local variables
LOCAL_SCOPE=0
Local() { # $*=Local variables names
  LOCAL_SCOPE=$(expr $LOCAL_SCOPE + 1)
  SetVar "LOCAL_${LOCAL_SCOPE}_VARS" "$*"
  for LOCAL_VAR in $* ; do
    if VarExists $LOCAL_VAR ; then
      SetVar "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR" "SetVar $LOCAL_VAR $(SingleQuote "$(VarValue $LOCAL_VAR)")"
    else
      SetVar "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR" "unset $LOCAL_VAR"
    fi
  done
}

# Restore the initial variables
EndLocal() {
  LOCAL_RETCODE=$?
  for LOCAL_VAR in $(VarValue "LOCAL_${LOCAL_SCOPE}_VARS") ; do
    eval $(VarValue "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR")
    unset "LOCAL_${LOCAL_SCOPE}_RESTORE_$LOCAL_VAR"
  done
  unset "LOCAL_${LOCAL_SCOPE}_VARS"
  LOCAL_SCOPE=$(expr $LOCAL_SCOPE - 1)
  return $LOCAL_RETCODE
}

#-----------------------------------------------------------------------------#
# Test routine

N=3
Test() {
  Local A B
  A=Before
  B=$N
  echo "#1 N=$N A='$A' B=$B"
  if [ $N -gt 0 ] ; then
    N=$(expr $N - 1)
    Test
  fi
  echo "#2 N=$N A='$A' B=$B"
  A="After "
  echo "#3 N=$N A='$A' B=$B"
  EndLocal
}

A="Initial value"
Test
echo "#0 N=$N A='$A' B=$B"

Output:

larvoire@JFLZB:/tmp$ ./LocalVars.sh
#1 N=3 A='Before' B=3
#1 N=2 A='Before' B=2
#1 N=1 A='Before' B=1
#1 N=0 A='Before' B=0
#2 N=0 A='Before' B=0
#3 N=0 A='After ' B=0
#2 N=0 A='Before' B=1
#3 N=0 A='After ' B=1
#2 N=0 A='Before' B=2
#3 N=0 A='After ' B=2
#2 N=0 A='Before' B=3
#3 N=0 A='After ' B=3
#0 N=0 A='Initial value' B=
larvoire@JFLZB:/tmp$

Using the same technique, I think it should be possible to dynamically detect if the local keyword is supported, and if it's not, define a new function called local that emulates it.
This way, the performance would be much better in the normal case of a modern shell having built-in locals. And things would still work on an old Posix shell without it.
Actually we'd need three dynamically generated functions:

  • BeginLocal, creating an empty pseudo-local scope, as is done in the beginning of my Local above, or doing nothing if the shell has built-in locals.
  • local, similar to my Local, defined only for shells not having built-in locals.
  • EndLocal, identical to mine, or just preserving the last exit code for shells having built-in locals.
Absorb answered 2/12, 2020 at 10:27 Comment(0)
A
0

Here's a quick function I wrote that first saves shell variables, then restores state after calling a function. Got the idea from @user7620483's answer, but it didn't work for me in dash.

I've also included set | md5sum lines to verify that the set function returns the same thing after both scope and func exit.

This kinda acts like a subshell in the sense that you can't really modify outside variables from inside the scoped function.

#!/bin/sh
set -eu

func() {
    VAR="Some value"
    printf "In func:     \"%s\"\n" "${VAR:-}"
}

scope() {
    # Save current environment variables
    __scope_saved_vars="$(set)"
    __scope_saved_vars_keys="$(\
        printf "%s\n" "${__scope_saved_vars}" | sed '/=/!d ; s/=.*$//'
    )"

    # Run the requested function
    "$@"

    # Restore old values
    eval "${__scope_saved_vars}"

    # Unset all new environment variables
    for __scope_new_key in $(set | sed '/=/!d ; s/=.*$//'); do
        printf "%s\n" "${__scope_saved_vars_keys}" | \
            grep -Fx "${__scope_new_key}" >/dev/null || {
            # New var after scope, unset it
            eval unset "${__scope_new_key}"
        }
    done
    unset __scope_new_key
}

printf "Empty:       \"%s\"\n" "${VAR:-}"
VAR="Hewwo"
printf "Before func: \"%s\"\n" "${VAR:-}"
set | md5sum

scope func

set | md5sum
printf "After func:  \"%s\"\n" "${VAR:-}"
Angleaangler answered 24/8, 2023 at 16:1 Comment(0)
W
-5

Define the functions using the function myfunc { syntax, and use typeset myvar to define your variables. If you define functions that way rather than using the myfunc(){ syntax, all of the common Bourne shells (bash, zsh, ksh '88 and '93) will localize variables defined with typeset (and aliases to typeset like integer).

Or reinvent the wheel. Whichever floats your boat. ;)

EDIT: while the question asks for POSIX, and this is not a POSIX-compliant function definition syntax, the person who asked indicates in a later comment that he's using bash. The use of "typset" in combination with the alternative function definition syntax is the best solution there, as the true POSIX mechanism requires the additional overhead of forking a new subshell.

Withers answered 4/9, 2013 at 3:40 Comment(4)
The function syntax won't work on implementations of the Almquist Shell: NetBSD, FreeBSD, Debian. You can search for man-pages of those with your favorite search engine.Crew
Bah. Like anyone uses Debian or Ubuntu or NetBSD or FreeBSD. ;)Withers
I hope that was some kind of sarcasm.Crew
Either I'm a sarcastic Linux admin who runs Ubuntu and NetBSD at home, or I'm a newbie who misuses the winking smilie for non-sarcastic purposes. Feel free to believe whichever you'd like. I'm accommodating that way.Withers

© 2022 - 2024 — McMap. All rights reserved.