Passaggio degli argomenti per riferimento
-
22-08-2019 - |
Domanda
Voglio chiedere se è possibile passare argomenti a una funzione di script per riferimento:
cioè.per fare qualcosa che assomiglierebbe a questo in 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
}
Quindi in BASH voglio fare qualcosa del tipo:
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
Ogni pensiero sarebbe apprezzato.
Soluzione
Siamo nel 2018 e questa domanda merita un aggiornamento.Almeno in Bash, a partire da Bash 4.3-alpha, puoi usare nomeref per passare gli argomenti della funzione per riferimento:
function boo()
{
local -n ref=$1
ref='new'
}
SOME_VAR='old'
echo $SOME_VAR # -> old
boo SOME_VAR
echo $SOME_VAR # -> new
I pezzi critici qui sono:
Passando il nome della variabile a boo, non il suo valore:
boo SOME_VAR
, nonboo $SOME_VAR
.All'interno della funzione, utilizzando
local -n ref=$1
dichiarare a nomerif alla variabile denominata da$1
, il che significa che non è un riferimento a$1
stesso, ma piuttosto ad una variabile il cui nome$1
tiene, cioèSOME_VAR
nel nostro caso.Il valore sul lato destro dovrebbe essere semplicemente una stringa che nomina una variabile esistente:non importa come ottieni la corda, quindi cose del generelocal -n ref="my_var"
Olocal -n ref=$(get_var_name)
funzionerebbe anche.declare
può anche sostituirelocal
in contesti che lo consentono/richiedono.Vedere capitolo sui parametri della shell nel Manuale di riferimento di Bash per maggiori informazioni.
Il vantaggio di questo approccio è (probabilmente) una migliore leggibilità e, soprattutto, l'evitabilità eval
, le cui insidie in termini di sicurezza sono numerose e ben documentate.
Altri suggerimenti
Dalla pagina man bash (Parametro di espansione):
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.
Quindi un riferimento è il nome della variabile. Ecco una funzione swap
utilizzando
indirezione variabile che non richiede una variabile temporanea:
function swap()
{ #
# @param VARNAME1 VARNAME2
#
eval "$1=${!2} $2=${!1}"
}
$ a=1 b=2
$ swap a b
$ echo $a $b
2 1
Con una funzione di supporto 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
}
E usare in questo modo dal di dentro Newfun()
:
local "$1" && upvar $1 new
Per tornare più variabili, utilizzare un'altra funzione di supporto upvars
. Questo consente il passaggio variabili multiple all'interno di una chiamata, evitando così possibili conflitti se una chiamata upvar
cambia una variabile utilizzata in un'altra chiamata upvar
successiva.
See: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference per la funzione di supporto upvars
e ulteriori informazioni.
Il problema con:
eval $1=new
è che non è sicuro se $1
accade a contenere un comando:
set -- 'ls /;true'
eval $1=new # Oops
Sarebbe meglio usare printf -v
:
printf -v "$1" %s new
Ma printf -v
non può assegnare gli array.
Inoltre, sia eval
e printf
non funzionano se la variabile sembra essere dichiarati local
:
g() { local b; eval $1=bar; } # WRONG
g b # Conflicts with `local b'
echo $b # b is empty unexpected
Il conflitto rimane lì anche se è local b
unset
:
g() { local b; unset b; eval $1=bar; } # WRONG
g b # Still conflicts with `local b'
echo $b # b is empty unexpected
Ho trovato un modo per fare questo, ma io non sono sicuro di come corretta è:
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
Quindi passiamo il nome della variabile in contrasto con il valore e quindi utilizzare eval ...
Bash non ha nulla di simile riferimenti integrati in esso, in modo sostanzialmente l'unico modo in cui si sarebbe in grado di fare quello che vuoi è quello di passare alla funzione il nome della variabile globale si desidera modificare. E anche allora avrete bisogno di una dichiarazione eval
:
boo() {
eval ${1}="new"
}
SOME_VAR="old"
echo $SOME_VAR # old
boo "SOME_VAR"
echo $SOME_VAR # new
Non credo che è possibile utilizzare riferimenti indiretti qui perché Bash accede automaticamente il valore della variabile il cui nome è memorizzato nel riferimento indiretto. Esso non ti dà la possibilità di impostarlo.
Ok, quindi la questione è stata in attesa di una soluzione di 'reale' da qualche tempo, e io sono felice di dire che ora possiamo raggiungere questo obiettivo senza usare eval a tutti.
Il chiave da ricordare è quello di dichiarare un riferimento sia il chiamante come il chiamato, almeno nel mio esempio:
#!/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 non dovrebbe mai essere utilizzato su una stringa che un utente può impostare perché la sua pericolosa. Qualcosa come "stringa; rm -rf ~" sarà male. Quindi in generale del suo meglio per trovare soluzioni in cui non dovete preoccuparvi di questo.
Tuttavia, saranno necessari eval per impostare le variabili passate, come il commento notato.
$ 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"
Output:
cat
cat|dog
cat|dog|hamster
Struttura per chiamare quella funzione è:
append_string name_of_var_to_update string_to_add name_of_var_containing_sep_char
Nome della variabile viene passata al fuction sugli animali domestici e settembre, mentre stringa da aggiungere viene passata come di consueto come valore. "$ {!} 1" si riferisce al contenuto della variabile globale ANIMALI. All'inizio tale variabile è vuota e Contens viene aggiunta ogni volta che chiamiamo la funzione. carattere di separazione può essere selezionata in base alle esigenze. "Eval" linee partire aggiornare variabile ANIMALI.
Questo è ciò che funziona per me su Ubuntu shell bash
#!/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