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.
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
, pasboo $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 à direSOME_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 commelocal -n ref="my_var"
oulocal -n ref=$(get_var_name)
fonctionneraient aussi. {[10] } peut également remplacerlocal
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.
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
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
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 ...
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.
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
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
#!/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.
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