Depois de ler as páginas do manual do bash e com relação a este post .
Ainda estou tendo problemas para entender o que exatamente o eval
comando faz e quais seriam seus usos típicos. Por exemplo, se o fizermos:
bash$ set -- one two three # sets $1 $2 $3
bash$ echo $1
one
bash$ n=1
bash$ echo ${$n} ## First attempt to echo $1 using brackets fails
bash: ${$n}: bad substitution
bash$ echo $($n) ## Second attempt to echo $1 using parentheses fails
bash: 1: command not found
bash$ eval echo \${$n} ## Third attempt to echo $1 using 'eval' succeeds
one
O que exatamente está acontecendo aqui e como o cifrão e a barra invertida se ligam ao problema?
$($n)
é executado$n
em um subshell. Ele tenta executar o comando1
que não existe.echo $1
eventualmente, não1
. Eu não acho que isso possa ser feito usando sub-conchas.eval
.Respostas:
eval
usa uma string como argumento e a avalia como se você tivesse digitado essa string em uma linha de comando. (Se você passar vários argumentos, eles primeiro serão unidos aos espaços entre eles.)${$n}
é um erro de sintaxe no bash. Dentro dos chavetas, você pode ter apenas um nome de variável, com alguns prefixos e sufixos possíveis, mas não pode ter sintaxe arbitrária de bash e, em particular, não pode usar expansão de variável. Existe uma maneira de dizer "o valor da variável cujo nome está nessa variável", no entanto:$(…)
executa o comando especificado entre parênteses em um subshell (ou seja, em um processo separado que herda todas as configurações, como valores de variáveis do shell atual), e reúne sua saída. Então,echo $($n)
é executado$n
como um comando shell e exibe sua saída. Desde$n
avalia para1
,$($n)
tenta executar o comando1
, que não existe.eval echo \${$n}
executa os parâmetros passados paraeval
. Após a expansão, os parâmetros sãoecho
e${1}
. Entãoeval echo \${$n}
executa o comandoecho ${1}
.Note-se que a maior parte do tempo, você deve usar aspas em torno substituições variáveis e substituições de comando (ou seja, sempre que houver uma
$
):"$foo", "$(foo)"
. Sempre coloque aspas duplas em torno de substituições de variáveis e comandos , a menos que você saiba que precisa deixá-las desativadas. Sem as aspas duplas, o shell realiza a divisão do campo (ou seja, divide o valor da variável ou a saída do comando em palavras separadas) e, em seguida, trata cada palavra como um padrão curinga. Por exemplo:eval
não é usado com muita frequência. Em algumas shells, o uso mais comum é obter o valor de uma variável cujo nome não é conhecido até o tempo de execução. No bash, isso não é necessário, graças à${!VAR}
sintaxe.eval
ainda é útil quando você precisa construir um comando mais longo contendo operadores, palavras reservadas etc.fonte
eval
recebe uma string (que pode ser o resultado da análise e avaliação) e a interpreta como um trecho de código.eval eval
. Não me lembro de ter sentido a necessidade. Se você realmente precisa de doiseval
passes, use uma variável temporária, vai ser mais fácil de depurar:eval tmp="\${$i}"; eval x="\${$tmp}"
.eval
apenas pega o código em uma string e o avalia de acordo com as regras usuais. Tecnicamente, isso nem está correto, porque existem alguns casos em que o Bash modifica a análise para nem mesmo fazer expansões nos argumentos de eval - mas esse é um boato muito obscuro do qual duvido que alguém conheça.Basta pensar em eval como "avaliar sua expressão mais uma vez antes da execução"
eval echo \${$n}
torna-seecho $1
após a primeira rodada de avaliação. Três alterações a serem observadas:\$
tornou-se$
(é necessária a barra invertida, caso contrário ele tenta avaliar${$n}
, o que significa uma variável chamada{$n}
, o que não é permitido)$n
foi avaliado para1
eval
desapareceuNa segunda rodada, é basicamente o
echo $1
que pode ser executado diretamente.Então
eval <some command>
, primeiro avaliaremos<some command>
(por avaliar aqui, quero dizer substituir variáveis, substituir caracteres de escape pelos corretos etc.) e, em seguida, executar a expressão resultante novamente.eval
é usado quando você deseja criar variáveis dinamicamente ou ler saídas de programas projetados especificamente para serem lidos assim. Veja http://mywiki.wooledge.org/BashFAQ/048 para exemplos. O link também contém algumas maneiras típicas deeval
uso e os riscos associados a ele.fonte
${VAR}
sintaxe é permitida e preferida quando houver alguma ambiguidade (faz$VAR == $V
, seguida porAR
ou$VAR == $VA
seguida porR
).${VAR}
é equivalente a$VAR
. De fato, o nome da variável$n
disso não é permitido.eval eval echo \\\${\${$i}}
fará uma desreferência tripla. Não tenho certeza se existe uma maneira mais simples de fazer isso. Além disso,\${$n}
funciona bem (impressõesone
) na minha máquina ..echo \\\${\${$i}}
imprime\${${n}}
.eval echo \\\${\${$i}}
é equivalente aecho \${${n}}`` and prints
$ {1}.
eval eval echo \\\ $ {\ $ {$ i}} `é equivalente aeval echo ${1}
e imprimeone
.` escapes the second one, and the third
`escapa$
depois disso. Assim torna-se\${${n}}
depois de uma rodada de avaliação\\
produzindo uma barra invertida. Então,\$
rendendo um dólar. E assim por diante.Na minha experiência, um uso "típico" de eval é para executar comandos que geram comandos shell para definir variáveis de ambiente.
Talvez você tenha um sistema que use uma coleção de variáveis de ambiente e tenha um script ou programa que determine quais devem ser definidas e seus valores. Sempre que você executa um script ou programa, ele é executado em um processo bifurcado; portanto, tudo o que faz diretamente nas variáveis de ambiente é perdido quando sai. Mas esse script ou programa pode enviar os comandos de exportação para o stdout.
Sem avaliação, você precisaria redirecionar stdout para um arquivo temporário, originar o arquivo temporário e excluí-lo. Com eval, você pode apenas:
Observe que as aspas são importantes. Veja este exemplo (artificial):
fonte
${varname}
. Acho conveniente usar arquivos .conf idênticos em vários servidores diferentes, com apenas algumas coisas parametrizadas por variáveis de ambiente. Eu editei / opt / lampp / xampp (que inicia o apache) para fazer esse tipo de avaliação com um script que vasculha o sistema e geraexport
instruções bash para definir variáveis para os arquivos .conf.eval "$(pyenv init -)"
no seu.bash_profile
arquivo de configuração do shell (ou similar). Isso constrói um pequeno script de shell e o avalia no shell atual.A instrução eval diz ao shell para pegar os argumentos de eval como comando e executá-los na linha de comando. É útil em uma situação como abaixo:
No seu script, se você estiver definindo um comando em uma variável e, posteriormente, desejar usar esse comando, use eval:
fonte
Atualização: algumas pessoas dizem que nunca se deve usar avaliação. Discordo. Eu acho que o risco surge quando uma entrada corrompida pode ser passada para
eval
. No entanto, existem muitas situações comuns em que isso não representa um risco e, portanto, vale a pena saber como usar o eval em qualquer caso. Essa resposta do stackoverflow explica os riscos da avaliação e as alternativas à avaliação. Por fim, cabe ao usuário determinar se / quando a avaliação é segura e eficiente de usar.A
eval
instrução bash permite executar linhas de código calculadas ou adquiridas pelo seu script bash.Talvez o exemplo mais direto seja um programa bash que abra outro script bash como um arquivo de texto, leia cada linha de texto e use
eval
para executá-los em ordem. Esse é essencialmente o mesmo comportamento dasource
instrução bash , que é o que se usaria, a menos que fosse necessário realizar algum tipo de transformação (por exemplo, filtragem ou substituição) no conteúdo do script importado.Raramente precisei
eval
, mas achei útil ler ou escrever variáveis cujos nomes estavam contidos em cadeias atribuídas a outras variáveis. Por exemplo, para executar ações em conjuntos de variáveis, mantendo o tamanho do código pequeno e evitando redundância.eval
é conceitualmente simples. No entanto, a sintaxe estrita do idioma bash e a ordem de análise do intérprete do bash podem ser diferenciadas eeval
parecer enigmáticas e difíceis de usar ou entender. Aqui estão os itens essenciais:O argumento passado para
eval
é uma expressão de cadeia que é calculada em tempo de execução.eval
executará o resultado final analisado de seu argumento como uma linha de código real em seu script.A sintaxe e a ordem de análise são rigorosas. Se o resultado não for uma linha executável de código bash, no escopo do seu script, o programa falhará na
eval
instrução ao tentar executar o lixo.Ao testar, você pode substituir a
eval
instruçãoecho
e ver o que é exibido. Se for um código legítimo no contexto atual, sua execuçãoeval
funcionará.Os exemplos a seguir podem ajudar a esclarecer como o eval funciona ...
Exemplo 1:
eval
A declaração na frente do código 'normal' é um NOPNo exemplo acima, as primeiras
eval
instruções não têm finalidade e podem ser eliminadas.eval
é inútil na primeira linha porque não há aspecto dinâmico no código, ou seja, ele já foi analisado nas linhas finais do código bash, portanto, seria idêntico como uma declaração normal de código no script bash. O segundo tambémeval
é inútil, porque, embora exista uma etapa de análise convertida$a
para o seu equivalente literal de cadeia de caracteres, não há indireção (por exemplo, nenhuma referência via valor da cadeia de um substantivo bash real ou variável de script realizada por bash), portanto se comportaria de forma idêntica como uma linha de código sem oeval
prefixo.Exemplo 2:
Execute a atribuição de var usando nomes de var passados como valores de sequência.
Se você fosse
echo $key=$val
, a saída seria:Que , sendo o resultado final da análise de cadeia, é o que será executado por eval, portanto, o resultado da instrução echo no final ...
Exemplo 3:
Incluindo mais Indireção no Exemplo 2
O exposto acima é um pouco mais complicado que o exemplo anterior, confiando mais na ordem de análise e nas peculiaridades do bash. A
eval
linha seria aproximadamente analisada internamente na seguinte ordem (observe que as seguintes instruções são pseudocódigo, não código real, apenas para tentar mostrar como a instrução seria dividida em etapas internamente para chegar ao resultado final) .Se a ordem de análise assumida não explica o que a avaliação está fazendo o suficiente, o terceiro exemplo pode descrever a análise com mais detalhes para ajudar a esclarecer o que está acontecendo.
Exemplo 4:
Descubra se os vars, cujos nomes estão contidos em cadeias, eles próprios contêm valores de cadeia.
Na primeira iteração:
Bash analisa o argumento
eval
eeval
vê literalmente isso em tempo de execução:O pseudocódigo a seguir tenta ilustrar como o bash interpreta a linha de código real acima , para chegar ao valor final executado por
eval
. (as seguintes linhas descritivas, não o código bash exato):Depois que toda a análise é feita, o resultado é o que é executado, e seu efeito é óbvio, demonstrando que não há nada particularmente misterioso sobre
eval
si mesmo, e a complexidade está na análise de seu argumento.O código restante no exemplo acima simplesmente testa para verificar se o valor atribuído a $ varval é nulo e, nesse caso, solicita que o usuário forneça um valor.
fonte
Inicialmente, intencionalmente nunca aprendi a usar o eval, porque a maioria das pessoas recomenda ficar longe dele como uma praga. No entanto, descobri recentemente um caso de uso que me fez ficar chateado por não reconhecê-lo antes.
Se você possui tarefas cron que deseja executar interativamente para testar, poderá visualizar o conteúdo do arquivo com cat e copiar e colar a tarefa cron para executá-lo. Infelizmente, isso envolve tocar o mouse, o que é um pecado no meu livro.
Digamos que você tenha um trabalho cron em /etc/cron.d/repeatme com o conteúdo:
*/10 * * * * root program arg1 arg2
Você não pode executar isso como um script com todo o lixo à sua frente, mas podemos usar o cut para se livrar de todo o lixo, envolvê-lo em uma subcama e executar a string com eval
eval $( cut -d ' ' -f 6- /etc/cron.d/repeatme)
O comando recortar imprime apenas o sexto campo do arquivo, delimitado por espaços. Eval então executa esse comando.
Eu usei um trabalho cron aqui como exemplo, mas o conceito é formatar o texto de stdout e depois avaliar esse texto.
O uso de eval neste caso não é inseguro, porque sabemos exatamente o que avaliaremos antes.
fonte
Recentemente, tive que usar
eval
para forçar várias expansões de braçadeiras a serem avaliadas na ordem em que eu precisava. O Bash faz várias expansões de chaves da esquerda para a direita, entãoexpande para
mas eu precisava da segunda expansão de braçadeira feita primeiro, produzindo
O melhor que pude fazer para isso foi
Isso funciona porque as aspas simples protegem o primeiro conjunto de chaves da expansão durante a análise da
eval
linha de comando, deixando que elas sejam expandidas pelo subshell invocado poreval
.Pode haver algum esquema astuto envolvendo expansões de chaves aninhadas que permita que isso aconteça em uma única etapa, mas se houver, sou velho e estúpido demais para vê-lo.
fonte
Você perguntou sobre usos típicos.
Uma queixa comum sobre scripts de shell é que você (supostamente) não pode passar por referência para recuperar valores das funções.
Mas, na verdade, via "eval", você pode passar por referência. O chamado pode devolver uma lista de atribuições de variáveis a serem avaliadas pelo chamador. É passado por referência porque o chamador pode especificar os nomes das variáveis de resultado - veja o exemplo abaixo. Os resultados dos erros podem ser transmitidos de volta aos nomes padrão, como errno e errstr.
Aqui está um exemplo de passagem por referência no bash:
A saída é assim:
Existe uma largura de banda quase ilimitada nessa saída de texto! E há mais possibilidades se as várias linhas de saída forem usadas: por exemplo, a primeira linha pode ser usada para atribuições de variáveis, a segunda para 'fluxo de pensamento' contínuo, mas isso está além do escopo deste post.
fonte
Gosto da resposta "avaliando sua expressão mais uma vez antes da execução" e gostaria de esclarecer com outro exemplo.
Os resultados curiosos da opção 2 são que teríamos passado dois parâmetros da seguinte maneira:
"value
content"
Como isso é contra-intuitivo? O adicional
eval
irá corrigir isso.Adaptado de https://stackoverflow.com/a/40646371/744133
fonte
Na pergunta:
gera erros alegando que os arquivos a e tty não existem. Entendi que isso significa que tty não está sendo interpretado antes da execução do grep, mas que o bash passou o tty como um parâmetro para o grep, que o interpretou como um nome de arquivo.
Há também uma situação de redirecionamento aninhado, que deve ser tratado por parênteses correspondentes que devem especificar um processo filho, mas o bash é primitivamente um separador de palavras, criando parâmetros a serem enviados para um programa, portanto, os parênteses não são correspondidos primeiro, mas interpretados como visto.
Eu fui específico com o grep e especifiquei o arquivo como um parâmetro em vez de usar um pipe. Também simplifiquei o comando base, passando a saída de um comando como um arquivo, para que a tubulação de E / S não fosse aninhada:
funciona bem.
não é realmente desejado, mas elimina o tubo aninhado e também funciona bem.
Em conclusão, o bash não parece gostar de pipping aninhado. É importante entender que o bash não é um programa de nova onda escrito de maneira recursiva. Em vez disso, o bash é um antigo programa 1,2,3, que foi anexado aos recursos. Para propósitos de garantir compatibilidade com versões anteriores, a maneira inicial de interpretação nunca foi modificada. Se o bash fosse reescrito para coincidir com parênteses pela primeira vez, quantos bugs seriam introduzidos em quantos programas bash? Muitos programadores adoram ser enigmáticos.
fonte