Pergunta

Eu quero perguntar se é possível passar argumentos para uma função de script por referência:

i. para fazer algo que seria parecido com isso em 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
}

Então em bash Quero fazer 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

Qualquer pensamento seria apreciado.

Foi útil?

Solução

É 2018, e esta questão merece uma atualização. Pelo menos em Bash, a partir das Bash 4.3-alpha, você pode usar namerefs para passar argumentos de função por referência:

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

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

As peças críticas aqui são:

  • Passando o nome da variável a vaia, não o seu valor:. boo SOME_VAR , não boo $SOME_VAR

  • Dentro da função, usando local -n ref=$1 para declarar um nameref para a variável chamada por $1, o que significa que não é uma referência a $1 si, mas sim a um variável cujo nome $1 detém, ou seja SOME_VAR no nosso caso. O valor no lado da mão direita deve ser apenas uma string com o nome de uma variável existente: não importa como você começa a corda, então coisas como local -n ref="my_var" ou local -n ref=$(get_var_name) iria trabalhar muito. declare também pode substituir local em contextos que permitem / exigem que. Consulte capítulo sobre Shell Parâmetros no Manual Bash Reference para mais informações.

A vantagem dessa abordagem é (discutivelmente) melhor legibilidade e, mais importante, evitando eval, cujo armadilhas de segurança são muitas e bem documentado.

Outras dicas

Do homem-página Bash (Parâmetro de Expansão):

    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.

Portanto, uma referência é o nome da variável. Aqui é uma função swap usando indireto variável que não requer uma variável temporária:

function swap()
{   # 
    # @param VARNAME1 VARNAME2
    #
    eval "$1=${!2} $2=${!1}"
}

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1

Use uma função 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
}

E usá-lo como este de dentro Newfun():

local "$1" && upvar $1 new

Para retornar múltiplas variáveis, use outra função auxiliar upvars. Isto permite passar múltiplas variáveis ??dentro de uma chamada, evitando assim possíveis conflitos se uma chamada upvar muda uma variável usada em outra chamada upvar posterior.

Veja: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference para upvars função auxiliar e obter mais informações.

O problema com:

eval $1=new

é que não é seguro se $1 acontece para conter um comando:

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

Seria melhor usar printf -v:

printf -v "$1" %s new

Mas printf -v não pode matrizes atribuir.

eval Além disso, tanto printf e local não vai funcionar se a variável passa a ser declarado:

g() { local b; eval $1=bar; }  # WRONG
g b                            # Conflicts with `local b'
echo $b                        # b is empty unexpected

As estadias de conflito lá mesmo 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

Eu encontrei uma maneira de fazer isso, mas eu não sei como corrigir isto é:

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

Então, passamos o nome da variável ao contrário do valor e, em seguida, usar eval ...

Bash não tem nada parecido com referências incorporadas a ele, então, basicamente a única maneira que você seria capaz de fazer o que você quer é passar a função o nome da variável global que você quer modificar. E mesmo assim você vai precisar de uma declaração eval:

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

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

Eu não acho que você pode usar referências indiretas aqui porque Bash acessa automaticamente o valor da variável cujo nome é armazenado na referência indireta. Ele não lhe dá a oportunidade de defini-lo.

Ok, assim que esta questão tem sido esperando por uma solução 'real' já há algum tempo, e eu estou contente de dizer que agora podemos fazer isso sem o uso de eval em tudo.

O tecla a lembrar é para declarar uma referência, tanto o chamador como o chamado, pelo menos no meu exemplo:

#!/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 deve ser usado em uma cadeia de caracteres que um usuário pode definir, porque é perigoso. Algo como "string; rm -rf ~" vai ser ruim. Então, geralmente o seu melhor para encontrar soluções em que você não precisa se preocupar com isso.

No entanto, eval serão necessários para definir as variáveis ??passadas, como o comentário observou.

$ 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

Estrutura para chamar essa função é:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

Nome da variável é passado para fuction sobre animais de estimação e setembro, enquanto string para anexar é passado da maneira usual como valor. "$ {! 1}" refere-se ao conteúdo da variável global PETS. No início dessa variável é vazia e contens é adicionado cada vez que chamar a função. caractere separador podem ser selecionados conforme necessário. "Eval" linhas começando atualização PETS variável.

Este é o que funciona para mim no shell bash Ubuntu

#!/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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top