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.

È stato utile?

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, non boo $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 genere local -n ref="my_var" O local -n ref=$(get_var_name) funzionerebbe anche. declare può anche sostituire local 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top