Le passage des arguments par référence
-
22-08-2019 - |
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.
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
, pasboo $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, à savoirSOME_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 commelocal -n ref="my_var"
oulocal -n ref=$(get_var_name)
travailleraient aussi.declare
peut également remplacerlocal
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