Passer des arguments par référence

Je veux demander s'il est possible de passer des arguments à une fonction de script par référence:

C'est-à-dire faire quelque chose qui ressemblerait à ceci en C:

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

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

Alors dans BASH je veux faire quelque chose comme:

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

Toute pensée serait appréciée.

26
demandé sur codeforester 2009-02-12 09:45:31

9 réponses

C'est 2018, et cette question mérite une mise à jour. Au moins dans Bash, à partir de Bash 4.3-alpha, vous pouvez utiliser namerefs pour passer des arguments de fonction par référence:

function boo() 
{
    local -n ref=$1
    ref='new' 
}

SOME_VAR='old'
echo $SOME_VAR # -> old
boo SOME_VAR
echo $SOME_VAR # -> new

Les pièces critiques ici sont:

  • En Passant de la variable nom de boo, pas sa valeur: boo SOME_VAR, pas boo $SOME_VAR.

  • L'Intérieur de la fonction, à l'aide de local -n=$1 pour déclarer un nameref de la variable nommée par $1, c'est pas un référence à $1 lui-même, mais plutôt à une variable , dont le nom $1 tient, c'est à dire SOME_VAR dans notre cas. La valeur sur le côté droit devrait juste être une chaîne nommant une variable existante: peu importe comment vous obtenez la chaîne, donc des choses comme local -n ref="my_var" ou local -n ref=$(get_var_name) fonctionneraient aussi. {[10] } peut également remplacer local dans les contextes qui l'autorisent/l'exigent. Voir chapitre sur les paramètres Shell dans Bash Reference Manual pour plus d'informations.

L'avantage de cette l'approche est (sans doute) une meilleure lisibilité et, surtout, éviter eval, dont les pièges de sécurité sont nombreux et bien documentés.

6
répondu Sergey Shevchenko 2018-09-21 00:17:58

Depuis la page de manuel Bash (extension des paramètres):

    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.

Par conséquent, une référence est le nom de la variable. Voici une fonction swap utilisant variable indirection qui ne nécessite pas de variable temporaire:

function swap()
{   # 
    # @param VARNAME1 VARNAME2
    #
    eval "$1=${!2} $2=${!1}"
}

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1
18
répondu Andreas Spindler 2013-02-13 11:25:44

Utiliser une fonction d'assistance 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
}

Et l'utiliser comme ceci de l'intérieur Newfun():

local "$1" && upvar $1 new

Pour renvoyer plusieurs variables, utilisez une autre fonction d'assistance upvars. Cela permet de passer plusieurs variables dans un appel, évitant ainsi d'éventuels conflits si un appel upvar change une variable utilisée dans un autre appel upvar suivant.

Voir: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference pour fonction d'assistance upvars et plus information.

Le problème avec:

eval $1=new

, c'Est qu'il n'est pas sûr si $1 contient une commande:

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

, Il serait préférable d'utiliser printf -v:

printf -v "$1" %s new

Mais printf -v ne peut pas assigner de tableaux.

De plus, eval et printf ne fonctionneront pas si la variable est déclarée local:

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

, Le conflit reste là, même si local b est unset:

g() { local b; unset b; eval $1=bar; }  # WRONG
g b                                     # Still conflicts with `local b'
echo $b                                 # b is empty unexpected
15
répondu Freddy Vulto 2010-05-17 20:19:37

J'ai trouvé un moyen de le faire mais je ne suis pas sûr de savoir à quel point c'est correct:

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

Nous passons donc le nom de la variable par opposition à la valeur, puis utilisons eval ...

10
répondu Roman M 2014-06-09 20:53:48

Bash n'a rien de tel que des références intégrées, donc fondamentalement, la seule façon de faire ce que vous voulez est de passer à la fonction le nom de la variable globale que vous voulez modifier. Et même alors, vous aurez besoin d'une instruction eval:

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

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

Je ne pense pas que vous puissiez utiliser des références indirectes ici car Bash accède automatiquement à la valeur de la variable dont le nom est stocké dans la référence indirecte. Cela ne vous donne pas la chance de le régler.

8
répondu David Z 2009-02-12 07:57:03

Ok, donc cette question attend une solution "réelle" depuis un certain temps maintenant, et je suis heureux de dire que nous pouvons maintenant accomplir cela sans utiliser eval du tout.

La Clé à retenir est de déclarer une référence à la fois dans l'appelant comme appelé, au moins dans mon exemple:

#!/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
4
répondu Aethalides 2016-09-22 16:33:47

Eval ne doit jamais être utilisé sur une chaîne qu'un utilisateur peut définir car c'est dangereux. Quelque chose comme "string; rm-rf ~" sera mauvais. Donc, généralement, il est préférable de trouver des solutions où vous n'avez pas à vous en soucier.

Cependant, eval sera nécessaire pour définir les variables passées, comme le commentaire l'a noté.

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
2
répondu Ian Kelling 2009-03-12 19:59:36
#!/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"

Sortie:

cat
cat|dog
cat|dog|hamster

La Structure pour appeler cette fonction est:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

Le nom de la variable est passé à fuction about PETS et SEP tandis que la chaîne à ajouter est passée de la manière habituelle en tant que valeur. "${!1} " fait référence au contenu de la variable global PETS. Au début, cette variable est vide et Content est ajouté chaque fois que nous appelons la fonction. Caractère séparateur peut être sélectionné au besoin. les lignes de départ" eval " mettent à jour la variable animaux de compagnie.

2
répondu ajaaskel 2014-10-10 19:25:48

C'est ce qui fonctionne pour moi sur Ubuntu Bash shell

#!/bin/sh

iteration=10

increment_count()
{
  local i
  i=$(($1+1))
  eval ${1}=\$i
}


increment_count iteration
echo $iteration #prints 11
increment_count iteration
echo $iteration #prints 12
0
répondu enthusiasticgeek 2017-07-05 15:55:33