Pregunta

Quiero preguntar si es posible pasar argumentos a una función de script por referencia:

es decir. hacer algo que se vería así 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
}

Así que en BASH quiero hacer algo como:

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

Cualquier idea sería apreciada.

¿Fue útil?

Solución

Es 2018, y esta cuestión merece una actualización. Por lo menos en Bash, a partir del golpe de 4,3-alfa, puede utilizar namerefs para pasar argumentos por referencia:

function boo() 
{
    local -n ref=$1
    ref='new' 
}

SOME_VAR='old'
echo $SOME_VAR # -> old
boo SOME_VAR
echo $SOME_VAR # -> new

Las piezas críticas aquí son:

  • Al pasar el nombre de la variable a abuchear, no su valor:. boo SOME_VAR , no boo $SOME_VAR

  • Dentro de la función, usando local -n ref=$1 para declarar un nameref para la variable nombrada por $1, lo que significa que no es una referencia a $1 en sí, sino más bien a una variables cuyo nombre $1 se mantiene, es decir SOME_VAR en nuestro caso. El valor en el lado derecho debe ser sólo una cadena con el nombre de una variable existente: no importa cómo se obtiene la cadena, así que cosas como local -n ref="my_var" o local -n ref=$(get_var_name) funcionarían también. declare también puede reemplazar local en contextos que permiten / requerir que. Ver capítulo sobre Parámetros de Shell en el Manual de Referencia de Bash para más información.

La ventaja de este enfoque es (posiblemente) mejor legibilidad y, lo más importante, evitando eval, cuyas trampas de seguridad son muchos y bien documentada.

Otros consejos

Desde la página de manual 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í es una función utilizando swap 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

Utilice un upvar función auxiliar:

# 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 utilizar de esta manera desde dentro Newfun():

local "$1" && upvar $1 new

Para devolver múltiples variables, utilizar 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 función de ayuda y más información.

El problema:

eval $1=new

es que no es seguro si $1 pasa a contener un comando:

set -- 'ls /;true'
eval $1=new  # Oops

Sería mejor utilizar printf -v:

printf -v "$1" %s new

Pero printf -v no puede asignar matrices.

Por otra parte, tanto eval y printf no funcionarán si la variable pasa a ser declarado 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 es 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

He encontrado una manera de hacer esto, pero no estoy seguro de cómo corregir esto es:

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

Así que pasamos el nombre de la variable en comparación con el valor y luego utilizar eval ...

Bash no tiene nada como referencias construido en él, así que básicamente la única manera de que sería capaz de hacer lo que quiere es pasar la función del nombre de la variable global que desea que modificar. E incluso entonces se necesita de una declaración eval:

boo() {
    eval ${1}="new"
}

SOME_VAR="old"
echo $SOME_VAR # old
boo "SOME_VAR"
echo $SOME_VAR # new

No creo que se puede utilizar referencias indirectas aquí porque Bash accede automáticamente el valor de la variable cuyo nombre se almacena en la referencia indirecta. Que no le da la oportunidad de configurarlo.

Ok, por lo que esta cuestión ha estado esperando una solución 'real' desde hace algún tiempo, y me alegra decir que ahora podemos lograr esto sin usar eval en absoluto.

La clave para recordar es declarar una referencia tanto en la persona que llama como el destinatario de la llamada, por lo 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

Eval nunca debe ser usado en una cadena de caracteres que un usuario puede establecer porque su peligroso. Algo así como "cadena; rm -rf ~" será malo. Por lo general todo lo posible para encontrar soluciones en las que usted no tiene que preocuparse por ello.

Sin embargo, será necesario eval para establecer las variables pasadas, como el comentario señaló.

$ 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"

Salida:

cat
cat|dog
cat|dog|hamster

Estructura para llamar esa función es:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

Nombre de la variable se pasa a fuction acerca de las mascotas y la SEP, mientras que la cadena para anexar se pasa de la forma habitual como valor. "$ {! 1}" se refiere al contenido de la variable global PETS. En el principio de que la variable está vacía y se añade contens cada vez que llamamos a la función. carácter separador se puede seleccionar según sea necesario. "Eval" líneas que comienzan actualizar la variable PETS.

Esto es lo que funciona para mí en 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top