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 réflexion sera apprécié.
Nous sommes en 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 les arguments de la 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:
Passer le nom de la variable à boo, pas sa valeur: boo SOME_VAR
, pas boo $SOME_VAR
.
Dans la fonction, utilisez local -n=$1
pour déclarer un nameref à la variable nommée par $1
, ce qui signifie qu'il ne s'agit pas d'une référence à $1
, mais à une variable dont le nom$1
tient, c’est-à-dire SOME_VAR
dans notre cas. La valeur à droite ne devrait être qu'une chaîne nommant une variable existante: peu importe la façon dont vous obtenez la chaîne, des éléments tels que local -n ref="my_var"
ou local -n ref=$(get_var_name)
fonctionneraient également. declare
peut également remplacer local
dans des contextes qui le permettent/le requièrent. Voir le chapitre sur les paramètres du shell dans le Manuel de référence Bash pour plus d'informations.
L’avantage de cette approche est (sans doute) une meilleure lisibilité et, surtout, d’éviter eval
, dont les pièges de sécurité sont nombreux et bien documentés.
Depuis la page de manuel Bash (Parameter Expansion):
Si le premier caractère du paramètre est un point d'exclamation (!), Un le niveau d'indirection variable est introduit. Bash utilise la valeur de la variable formée à partir du reste du paramètre sous le nom du variable; cette variable est ensuite développée et cette valeur est utilisée dans le reste de la substitution, plutôt que la valeur du paramètre lui-même. C'est ce qu'on appelle l'expansion indirecte .
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
Utilisez 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 utilisez-le comme ceci depuis Newfun()
:
local "$1" && upvar $1 new
Pour renvoyer plusieurs variables, utilisez une autre fonction d'assistance upvars
. Cela permet de transmettre plusieurs variables au sein d'un appel, évitant ainsi les conflits éventuels si un appel upvar
modifie une variable utilisée dans un autre appel upvar
ultérieur.
Voir: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference pour la fonction d'assistance upvars
et plus d'informations.
Le problème avec:
eval $1=new
est-ce que ce 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 affecter 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 y reste 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 faire cela 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 donc le nom de la variable par opposition à la valeur, puis utilisons eval ...
Bash n'a pas de références intégrées, alors la seule façon de faire ce que vous voulez est de transmettre à la fonction le nom de la variable globale que vous souhaitez modifier. Et même dans ce cas, vous aurez besoin d'une déclaration 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 définir.
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 consiste à déclarer une référence dans l'appelant en tant qu'appelé, du 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
#!/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 permettant d’appeler cette fonction est la suivante:
append_string name_of_var_to_update string_to_add name_of_var_containing_sep_char
Le nom de la variable est transmis aux fonctions PETS et SEP, tandis que la chaîne à ajouter est transmise de la manière habituelle. "$ {! 1}" fait référence au contenu de la variable globale PETS. Au début, cette variable est vide et le contenu est ajouté à chaque fois que nous appelons la fonction. Le caractère de séparation peut être sélectionné selon les besoins. Les lignes de départ "eval" mettent à jour la variable PETS.
Eval ne doit jamais être utilisé sur une chaîne qu'un utilisateur peut définir parce que c'est dangereux. Quelque chose comme "string; rm -rf ~" sera mauvais. Il est donc généralement préférable de trouver des solutions qui vous évitent de vous en inquiéter.
Cependant, eval sera nécessaire pour définir les variables transmises, comme indiqué dans le commentaire.
$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
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