views:

1540

answers:

5

Hi all,

I want to ask if it is possible to pass arguments to a script function by reference:

i.e. to do something that would look like this in C:

Void boo (int & myint) { myint= 5; }

main (){
    int t= 4;
    printf t; // t->4
    boo (t);
    printf t; // t ->5
}

So then in BASH I want to do somthing like:

Function boo () 
{

    var1=$1       # now var1 is global to the scrip but using it outside
                  # this function makes me loose encapsulation

    local var2=$1 # so i should use a local variable ... but how to pass it back ?

    var2=”new”    # only changes the local copy 
    #$1=”new”     this is wrong of course ...
    # ${!1}="new" # can i somehow use indirect reference?
}     

# call boo
SOME_VAR=”old
echo $SOME_VAR # -> old
boo “$SOME_VAR”
echo $SOME_VAR # -> new

Any thoughts would be appreciated.

+1  A: 

I have found a way to do this but I am not sure how correct this is:

Newfun()
{
    local var1=”$1”
    eval $var1=2
    # or can do eval $1=2 if no local var
}

var=1
echo  var is $var    # $var = 1
newfun “var”         # pass the name of the variable…
echo now var is $var # $var = 2

So we pass the variable name as opposed to the value and then use eval ...

Roman M
AFAIK, this is the only solution, and even this will only work global variables.
David Hanak
Same thing I posted... I didn't see yours on account of the site being down for a while.
David Zaslavsky
ya sorry i asked it a while ago and found an answer myself in the mean while ... i still give you credo for taking the time to answer :)
Roman M
+1  A: 

Bash doesn't have anything like references built into it, so basically the only way you would be able to do what you want is to pass the function the name of the global variable you want it to modify. And even then you'll need an eval statement:

boo() {
    eval ${1}="new"
}

SOME_VAR="old"
echo $SOME_VAR # old
boo "SOME_VAR"
echo $SOME_VAR # new

I don't think you can use indirect references here because Bash automatically accesses the value of the variable whose name is stored in the indirect reference. It doesn't give you the chance to set it.

David Zaslavsky
thats what i figured, but being new to bash it seemed weird that to pass the variable as if "by reference" you have to pass the quoted variable name ...
Roman M
+1  A: 

Eval should never be used on a string that a user can set because its dangerous. Something like "string; rm -rf ~" will be bad. So generally its best to find solutions where you don't have to worry about it.

However, eval will be needed to set the passed variables, as the comment noted.

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
Ian Kelling
but can you use it to set the variable, i.e. ${!x}=5
Roman M
A: 

Use a helper function upvar:

# Assign variable one scope above the caller.
# Usage: local "$1" && upvar $1 value [value ...]
# Param: $1  Variable name to assign value to
# Param: $*  Value(s) to assign.  If multiple values, an array is
#            assigned, otherwise a single value is assigned.
# NOTE: For assigning multiple variables, use 'upvars'.  Do NOT
#       use multiple 'upvar' calls, since one 'upvar' call might
#       reassign a variable to be used by another 'upvar' call.
# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
         fi
    fi
}

And use it like this from within Newfun():

local "$1" && upvar $1 new

For returning multiple variables, use another helper function upvars. This allows passing multiple variables within one call, thus avoiding possible conflicts if one upvar call changes a variable used in another subsequent upvar call.

See: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference for helper function upvars and more information.

The problem with:

eval $1=new

is that it's not safe if $1 happens to contain a command:

set -- 'ls /;true'
eval $1=new  # Oops

It would be better to use printf -v:

printf -v "$1" %s new

But printf -v cannot assign arrays.

Moreover, both eval and printf won't work if the variable happens to be declared local:

g() { local b; eval $1=bar; }  # WRONG
g b                            # Conflicts with `local b'
echo $b                        # b is empty unexpected

The conflict stays there even if local b is unset:

g() { local b; unset b; eval $1=bar; }  # WRONG
g b                                     # Still conflicts with `local b'
echo $b                                 # b is empty unexpected
Freddy Vulto