Passing arguments by reference

后端 未结 9 1911
挽巷
挽巷 2020-12-01 01:13

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++:



        
相关标签:
9条回答
  • 2020-12-01 01:44
    #!/bin/bash
    
    append_string()
    {
    if [ -z "${!1}" ]; then
    eval "${1}='$2'"
    else
    eval "${1}='${!1}''${!3}''$2'"
    fi
    }
    
    PETS=''
    SEP='|'
    append_string "PETS" "cat" "SEP"
    echo "$PETS"
    append_string "PETS" "dog" "SEP"
    echo "$PETS"
    append_string "PETS" "hamster" "SEP"
    echo "$PETS"
    

    Output:

    cat
    cat|dog
    cat|dog|hamster
    

    Structure for calling that function is:

    append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char
    

    Name of variable is passed to fuction about PETS and SEP while string to append is passed the usual way as value. "${!1}" refers to contents of global PETS variable. In the beginning that variable is empty and contens is added each time we call the function. Separator character can be selected as needed. "eval" starting lines update PETS variable.

    0 讨论(0)
  • 2020-12-01 01:47

    Ok, so this question has been waiting for a 'real' solution for some time now, and I am glad to say that we can now accomplish this without using eval at all.

    The key to remember is to declare a reference in both the caller as the callee, at least in my example:

    #!/bin/bash
    
    # NOTE this does require a bash version >= 4.3
    
    set -o errexit -o nounset -o posix -o pipefail
    
    passedByRef() {
    
        local -n theRef
    
        if [ 0 -lt $# ]; then
    
            theRef=$1
    
            echo -e "${FUNCNAME}:\n\tthe value of my reference is:\n\t\t${theRef}"
    
            # now that we have a reference, we can assign things to it
    
            theRef="some other value"
    
            echo -e "${FUNCNAME}:\n\tvalue of my reference set to:\n\t\t${theRef}"
    
        else
    
            echo "Error: missing argument"
    
            exit 1
        fi
    }
    
    referenceTester() {
    
        local theVariable="I am a variable"
    
        # note the absence of quoting and escaping etc.
    
        local -n theReference=theVariable
    
        echo -e "${FUNCNAME}:\n\tthe value of my reference is:\n\t\t${theReference}"
    
        passedByRef theReference
    
        echo -e "${FUNCNAME}:\n\tthe value of my reference is now:\n\t\t${theReference},\n\tand the pointed to variable:\n\t\t${theVariable}"
    
    }
    
    # run it
    
    referenceTester
    
    0 讨论(0)
  • 2020-12-01 01:50

    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 ...

    0 讨论(0)
  • 2020-12-01 01:55

    From the Bash man-page (Parameter Expansion):

        If the first  character of parameter is an exclamation  point (!), a
        level of variable indirection is  introduced. Bash uses the value of
        the variable  formed from the rest  of parameter as the  name of the
        variable; this variable  is then expanded and that value  is used in
        the rest  of the  substitution, rather than  the value  of parameter
        itself. This is known as indirect expansion.
    

    Therefore a reference is the variable's name. Here is a swap function using variable indirection that does not require a temporary variable:

    function swap()
    {   # 
        # @param VARNAME1 VARNAME2
        #
        eval "$1=${!2} $2=${!1}"
    }
    
    $ a=1 b=2
    $ swap a b
    $ echo $a $b
    2 1
    
    0 讨论(0)
  • 2020-12-01 02:00

    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.

    0 讨论(0)
  • 2020-12-01 02:01

    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
    
    0 讨论(0)
提交回复
热议问题