Como atribuir valores contendo espaço a variáveis no bash usando eval

19

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?

    
por Sébastien Clément 11.09.2014 / 22:08

4 respostas

13

Não use o eval, use declare

$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<
    
por 11.09.2014 / 22:13
11

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")"
    
por 11.09.2014 / 22:14
4

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 .

    
por 11.09.2014 / 23:54
1

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"

OUTPUT

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.

    
por 12.09.2014 / 09:58