Não use o eval, use declare
$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<
Eu quero atribuir dinamicamente valores a variáveis usando eval
.
O seguinte exemplo fictício funciona:
var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange
No entanto, quando o valor da variável contiver espaços, eval
retornará um erro, mesmo se $var_value
for colocado entre aspas duplas:
var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found
Qualquer maneira de contornar isso?
Não use o eval, use declare
$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<
Não use eval
para isso; use declare
.
var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"
Observe que a divisão de palavras não é um problema, pois tudo o que segue o =
é tratado como o valor por declare
, não apenas a primeira palavra.
Em bash
4.3, as referências nomeadas tornam isso um pouco mais simples.
$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange
Você pode fazer eval
funcionar, mas você ainda não deve :) Usando eval
é um mau hábito para entrar.
$ eval "$(printf "%q=%q" "$var_name" "$var_value")"
Uma boa maneira de trabalhar com eval
é substituí-lo por echo
para teste. echo
e eval
funcionam da mesma forma (se reservarmos a expansão \x
feita por algumas implementações echo
como bash
em algumas condições).
Ambos os comandos unem seus argumentos com um espaço entre eles. A diferença é que echo
exibe o resultado enquanto eval
avalia / interpreta como código shell o resultado.
Então, para ver qual código de shell
eval $(echo $var_name=$var_value)
avaliaria, você pode executar:
$ echo $(echo $var_name=$var_value)
fruit=blue orange
Não é isso que você quer, o que você quer é:
fruit=$var_value
Além disso, usar $(echo ...)
aqui não faz sentido.
Para exibir o acima, você executaria:
$ echo "$var_name=\$var_value"
fruit=$var_value
Então, para interpretar isso, é simplesmente:
eval "$var_name=\$var_value"
Observe que ele também pode ser usado para definir elementos de matriz individuais:
var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"
Como outros já disseram, se você não quer que seu código seja bash
específico, você pode usar declare
como:
declare "$var_name=$var_value"
No entanto, note que ele tem alguns efeitos colaterais.
Limita o escopo da variável à função em que é executada. Por isso, você não pode usá-la, por exemplo, em coisas como:
setvar() {
var_name=$1 var_value=$2
declare "$var_name=$var_value"
}
setvar foo bar
Porque isso declararia uma variável foo
local como setvar
, por isso seria inútil.
bash-4.2
adicionou uma opção -g
para declare
declarar uma variável global , mas não é isso que queremos, pois nosso setvar
definiria um global var ao contrário do chamador se o chamador era uma função, como em:
setvar() {
var_name=$1 var_value=$2
declare -g "$var_name=$var_value"
}
foo() {
local myvar
setvar myvar 'some value'
echo "1: $myvar"
}
foo
echo "2: $myvar"
que produziria:
1:
2: some value
Além disso, observe que enquanto declare
é chamado declare
(na verdade bash
emprestou o conceito do typeset
construído do shell Korn), se a variável já estiver definida, declare
não declara um novo variável e a maneira como a atribuição é feita depende do tipo da variável.
Por exemplo:
varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"
produzirá um resultado diferente (e potencialmente terá efeitos colaterais desagradáveis) se varname
tiver sido declarado anteriormente como escalar , matriz ou matriz associativa .
Se você fizer isso:
eval "$name=\$val"
... e $name
contém ;
- ou qualquer outro tokens que o shell possa interpretar como delimitador de um comando simples - precedido pela sintaxe adequada do shell, que será executada.
name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"
hi
be careful with eval
Às vezes, é possível separar a avaliação e execução de tais declarações. Por exemplo, alias
pode ser usado para pré-avaliar um comando. No exemplo a seguir, a definição da variável é salva em um alias
que só pode ser declarado com êxito se a variável $nm
que está avaliando não contiver bytes que não correspondam a alfanuméricos ASCII ou _
.
LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD
eval
é usado aqui para manipular o novo alias
de um varname. Mas isso só é chamado se a definição alias
anterior for bem-sucedida e, embora eu saiba que muitas implementações diferentes aceitarão muitos tipos diferentes de valores para alias
nomes, ainda não encontrei um que aceitará um completamente vazio.
A definição dentro de alias
é para _$nm
, no entanto, e isso é para garantir que nenhum valor de ambiente significativo seja gravado. Não conheço nenhum valor de ambiente digno de nota que comece com _
e normalmente é uma aposta segura para uma declaração semi-privada.
De qualquer forma, se a definição alias
for bem sucedida, ela irá declarar um alias
com o valor de $nm
. E eval
só chamará esse alias
se também não começar com um número - senão eval
obtém apenas um argumento nulo. Portanto, se ambas as condições forem atendidas, eval
chamará o alias e a definição da variável salva no alias será feita, após o que o novo alias
será prontamente removido da tabela de hash.