bash - programacion - programar shell script linux
Bash-Pasar argumentos por referencia (8)
Quiero preguntar si es posible pasar argumentos a una función de script por referencia:
es decir, para hacer algo que se vería así en C:
Void boo (int & myint) { myint= 5; }
main (){
int t= 4;
printf t; // t->4
boo (t);
printf t; // t ->5
}
Entonces en BASH quiero hacer algo como:
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
Cualquier pensamiento sería apreciado.
Bash no tiene nada como referencias integradas, por lo que básicamente la única forma en que podría hacer lo que desea es pasarle a la función el nombre de la variable global que desea que modifique. E incluso entonces necesitarás una declaración de eval
:
boo() {
eval ${1}="new"
}
SOME_VAR="old"
echo $SOME_VAR # old
boo "SOME_VAR"
echo $SOME_VAR # new
No creo que pueda usar referencias indirectas porque Bash accede automáticamente al valor de la variable cuyo nombre está almacenado en la referencia indirecta. No te da la oportunidad de configurarlo.
Eval nunca debe usarse en una cadena que un usuario puede establecer porque es peligrosa. Algo como "string; rm -rf ~" será malo. Por lo general, lo mejor es encontrar soluciones donde no tenga que preocuparse.
Sin embargo, se necesitará evaluar para establecer las variables aprobadas, como se señaló en el comentario.
$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
Use una función auxiliar 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
}
Y Newfun()
dentro de Newfun()
:
local "$1" && upvar $1 new
Para devolver múltiples variables, use otra función auxiliar upvars
. Esto permite pasar múltiples variables dentro de una llamada, evitando así posibles conflictos si una llamada upvar
cambia una variable utilizada en otra llamada upvar
posterior.
Ver: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference para upvars
funciones upvars
y más información.
El problema con:
eval $1=new
es que no es seguro si $1
contiene un comando:
set -- ''ls /;true''
eval $1=new # Oops
Sería mejor usar printf -v
:
printf -v "$1" %s new
Pero printf -v
no puede asignar matrices.
Además, tanto eval
como printf
no funcionarán si la variable pasa a ser declarada local
:
g() { local b; eval $1=bar; } # WRONG
g b # Conflicts with `local b''
echo $b # b is empty unexpected
El conflicto permanece allí incluso 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
Desde la página de manual de Bash (Expansión de parámetros):
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.
Por lo tanto, una referencia es el nombre de la variable. Aquí hay una función de swap
que utiliza la indirección variable que no requiere una variable temporal:
function swap()
{ #
# @param VARNAME1 VARNAME2
#
eval "$1=${!2} $2=${!1}"
}
$ a=1 b=2
$ swap a b
$ echo $a $b
2 1
He encontrado una manera de hacerlo, pero no estoy seguro de qué tan correcto es esto:
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
Entonces pasamos el nombre de la variable en lugar del valor y luego usamos eval ...
#!/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"
Salida:
cat
cat|dog
cat|dog|hamster
La estructura para llamar a esa función es:
append_string name_of_var_to_update string_to_add name_of_var_containing_sep_char
El nombre de la variable se pasa a fuction sobre PETS y SEP, mientras que la cadena para agregar se pasa de la manera habitual como valor. "$ {! 1}" se refiere a los contenidos de la variable global PETS. Al principio, esa variable está vacía y contens se agrega cada vez que llamamos a la función. El carácter separador se puede seleccionar según sea necesario. Las líneas de partida "eval" actualizan la variable PETS.
Bien, esta pregunta ha estado esperando una solución "real" desde hace algún tiempo, y me complace decir que ahora podemos lograr esto sin usar ninguna evaluación.
La clave para recordar es declarar una referencia tanto en la persona que llama como la llamada, al menos en mi ejemplo:
#!/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
Esto es lo que funciona para mí en 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