Question

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

i.e.. faire quelque chose qui ressemblerait à ceci en C ++:

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

int main() {
    int t = 4;
    printf("%d\n", t); // t->4
    boo(t);
    printf("%d\n", t); // t->5
}

Alors dans BASH je veux faire quelque chose comme:

function boo () 
{

    var1=$1       # now var1 is global to the script but using it outside
                  # this function makes me lose 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

Toutes les pensées seraient appréciés.

Était-ce utile?

La solution

Il est 2018, et cette question mérite une mise à jour. Au moins dans Bash, au 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 le nom de la variable à huer, et non sa valeur. boo SOME_VAR , pas boo $SOME_VAR

  • Dans la fonction, en utilisant local -n ref=$1 pour déclarer nameref à la variable nommée par $1, ce qui signifie qu'il n'est pas une référence à lui-même $1, mais plutôt à un variable dont le nom $1 détient, à savoir SOME_VAR dans notre cas. La valeur de la droite doit juste être une chaîne nommant une variable existante: peu importe la façon dont vous obtenez la chaîne, de sorte que des choses comme local -n ref="my_var" ou local -n ref=$(get_var_name) travailleraient aussi. declare peut également remplacer local dans des contextes qui permettent / exiger. Voir chapitre sur les paramètres Shell dans le Manuel de référence Bash plus d'informations.

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

Autres conseils

De l'homme Bash page (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 à l'aide indirection variable qui ne nécessite pas une variable temporaire:

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

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1

Utilisez une fonction d'aide 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 à l'intérieur Newfun():

local "$1" && upvar $1 new

Pour revenir plusieurs variables, utilisez une autre fonction d'aide upvars. Cela permet de passer plusieurs variables au sein d'un appel, évitant ainsi les conflits possibles si un appel upvar change une variable utilisée dans un autre appel ultérieur de upvar.

Voir: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference pour la fonction d'aide upvars et plus d'informations.

Le problème:

eval $1=new

est que ce n'est pas sûr si $1 arrive à contenir 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 affecter les tableaux.

En outre, les deux eval et printf ne fonctionnera pas si la variable se trouve être 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

Je l'ai trouvé un moyen de le faire, mais je ne suis pas sûr que ce soit 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 le nom de la variable, par opposition à la valeur puis utilisez eval ...

Bash n'a pas quelque chose comme des références construites en elle, donc en gros la seule façon que vous seriez en mesure de faire ce que vous voulez est de faire passer la fonction du nom de la variable globale que vous souhaitez modifier. Et même alors, vous aurez besoin d'une déclaration de 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 pouvez utiliser des références indirectes ici parce que Bash accède automatiquement à la valeur de la variable dont le nom figure dans la référence indirecte. Il ne vous donne pas la chance de le régler.

Ok, donc cette question est en attente d'une solution « réel » depuis un certain temps, et je suis heureux de dire que nous pouvons maintenant accomplir cela sans utiliser eval du tout.

touche à retenir est de déclarer une référence à la fois l'appelant que l'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

Eval ne doit jamais être utilisé sur une chaîne qu'un utilisateur peut définir parce que son dangereux. Quelque chose comme « string, rm -rf ~ » sera mauvais. Donc, en général de son mieux pour trouver des solutions où vous n'avez pas à vous inquiéter à ce sujet.

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

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
#!/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

Structure pour appeler cette fonction est:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

Nom de la variable est passée à Fuction sur les animaux et SEP, tandis que la chaîne à ajouter est passée comme valeur de la manière habituelle. "$ {!} 1" fait référence au contenu de la variable globale ANIMAUX. Au début cette variable est vide et Contens est ajouté chaque fois que nous appelons la fonction. Caractère de séparation peut être sélectionné en fonction des besoins. "Eval" lignes de départ mise à jour variables ANIMAUX.

est ce qui fonctionne pour moi shell bash Ubuntu

#!/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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top