De todos os comandos Bash, o pobre e velho eval
provavelmente tem a pior reputação. Justificado ou apenas má publicidade? Discutimos o uso e os perigos desse comando menos apreciado do Linux.
Índice
Precisamos conversar sobre avaliação
Usado de forma descuidada, eval
pode levar a comportamentos imprevisíveis e até mesmo a inseguranças do sistema. Pelo que parece, provavelmente não deveríamos usá-lo, certo? Bem, não exatamente.
Você poderia dizer algo semelhante sobre automóveis. Nas mãos erradas, são uma arma mortal. As pessoas os usam em ataques de aríetes e como veículos de fuga. Deveríamos todos parar de usar carros? Não, claro que não. Mas têm de ser utilizados de forma adequada e por pessoas que saibam conduzi-los.
O adjetivo usualmente aplicado eval
é “mal”. Mas tudo se resume a como está sendo usado. O
eval
O comando agrupa os valores de uma ou mais variáveis . Ele cria uma string de comando. Em seguida, ele executa esse comando. Isso o torna útil quando você precisa lidar com situações em que o conteúdo de um comando é derivado dinamicamente durante a execução do seu script .
Os problemas surgem quando um script é escrito para ser usado eval
em uma string recebida de algum lugar fora do script. Pode ser digitado por um usuário, enviado por meio de uma API, marcado em uma solicitação HTTPS ou em qualquer outro lugar externo ao script.
Se a cadeia de caracteres na qual eval
irá funcionar não foi derivada localmente e programaticamente, existe o risco de a cadeia de caracteres conter instruções maliciosas incorporadas ou outras entradas mal formadas. Obviamente, você não deseja eval
executar comandos maliciosos. Portanto, para garantir, não use eval
strings geradas externamente ou entradas do usuário.
Primeiros passos com avaliação
O eval
comando é um comando interno do shell Bash. Se Bash estiver presente, eval
estará presente.
eval
concatena seus parâmetros em uma única string. Ele usará um único espaço para separar os elementos concatenados. Ele avalia os argumentos e então passa a string inteira para o shell executar.
Vamos criar uma variável chamada
wordcount
.
wordcount="wc -w raw-notes.md"
A variável string contém um comando para contar as palavras em um arquivo chamado “raw-notes.md”.
Podemos usar eval
para executar esse comando passando-lhe o valor da variável.
eval "$wordcount"
O comando é executado no shell atual, não em um subshell. Podemos facilmente mostrar isso. Temos um pequeno arquivo de texto chamado “variables.txt”. Ele contém essas duas linhas.
primeiro = Como fazersegundo = Geek
Usaremos cat
para enviar essas linhas para a janela do terminal. Em seguida, usaremos eval
para avaliar um cat
comando para que as instruções dentro do arquivo de texto sejam executadas. Isso definirá as variáveis para nós.
variáveis de gato.txtavaliação "$(cat variáveis.txt)"
echo $ primeiro $ segundo
Ao echo
imprimir os valores das variáveis, podemos ver que eval
o comando é executado no shell atual, não em um subshell.
Um processo em um subshell não pode alterar o ambiente shell do pai. Como eval é executado no shell atual, as variáveis definidas por eval
podem ser utilizadas no shell que iniciou o eval
comando.
Observe que se você usar eval
em um script, o shell que será alterado eval
será o subshell em que o script está sendo executado, não o shell que o iniciou.
Usando variáveis na string de comando
Podemos incluir outras variáveis nas strings de comando. Definiremos duas variáveis para armazenar números inteiros.
num1=10num2=7
Criaremos uma variável para armazenar um expr
comando que retornará a soma de dois números. Isso significa que precisamos acessar os valores das duas variáveis inteiras no comando. Observe os crases em torno da expr
declaração.
add="`expr $num1 + $num2`"
Criaremos outro comando para nos mostrar o resultado da expr
instrução.
mostrar = "eco"
Observe que não precisamos incluir um espaço no final da echo
string, nem no início da expr
string. eval
cuida disso.
E para executar todo o comando usamos:
avaliar $ mostrar $ adicionar
Os valores das variáveis dentro da expr
string são substituídos na string por eval
, antes de serem passados para o shell para serem executados.
Acessando Variáveis Dentro de Variáveis
Você pode atribuir um valor a uma variável e, em seguida, atribuir o nome dessa variável a outra variável. Usando eval
, você pode acessar o valor contido na primeira variável, a partir do seu nome que é o valor armazenado na segunda variável. Um exemplo irá ajudá-lo a desvendar isso.
Copie este script para um editor e salve-o como um arquivo chamado “assign.sh”.
#!/bin/bashtitle="Como fazer Geek"
página da web=título
comando = "eco"
avaliação $comando \${$página da web}
Precisamos torná-lo executável com o chmod
comando .
chmod +x atribuir.sh
Você precisará fazer isso para todos os scripts copiados deste artigo. Basta usar o nome de script apropriado em cada caso.
Quando executamos nosso script, vemos o texto da variável, title
mesmo que o eval
comando esteja usando a variável webpage
.
./atribuir.sh
O cifrão escapado ” $
” e as chaves ” {}
” fazem com que eval observe o valor contido dentro da variável cujo nome está armazenado na webpage
variável.
Usando variáveis criadas dinamicamente
Podemos usar eval
para criar variáveis dinamicamente. Este script é chamado de “loop.sh”.
#!/bin/bashtotal=0
label="Loop concluído. Total:"
para n em {1..10}
fazer
avaliação x$n=$n
echo "Loop" $x$n
((total+=$x$n))
feito
ecoar $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10
eco $ rótulo $ total
Ele cria uma variável chamada total
que contém a soma dos valores das variáveis que criamos. Em seguida, ele cria uma variável de string chamada label
. Esta é uma sequência simples de texto.
Faremos um loop 10 vezes e criaremos 10 variáveis chamadas x1
até x10
. A eval
instrução no corpo do loop fornece o “x” e utiliza o valor do contador do loop $n
para criar o nome da variável. Ao mesmo tempo, ele define a nova variável para o valor do contador de loop $n
.
Ele imprime a nova variável na janela do terminal e então incrementa a total
variável com o valor da nova variável.
Fora do loop, as 10 novas variáveis são impressas mais uma vez, todas em uma linha. Observe que também podemos nos referir às variáveis pelos seus nomes reais, sem usar uma versão calculada ou derivada de seus nomes.
Finalmente, imprimimos o valor da total
variável.
./loop.sh
Usando eval com matrizes
Imagine um cenário em que você tem um script de longa execução e realizando algum processamento para você. Ele grava em um arquivo de log com um nome criado a partir de um carimbo de data/hora . Ocasionalmente, ele iniciará um novo arquivo de log. Quando o script for concluído, se não houver erros, ele excluirá os arquivos de log criados.
Você não quer que isso aconteça simplesmente rm *.log
, você só quer que ele exclua os arquivos de log que ele criou. Este script simula essa funcionalidade. Este é “clear-logs.sh”.
#!/bin/bashdeclare -a arquivos de log
contagem de arquivos=0
rm_string="eco"
função criar_logfile() {
((++contagem de arquivos))
nome do arquivo=$(data +"%Y-%m-%d_%H-%M-%S").log
arquivos de log[$filecount]=$nome do arquivo
echo $filecount "Criado" ${logfiles[$filecount]}
}
#corpo do script. Algum processamento é feito aqui que
# gera periodicamente um arquivo de log. Vamos simular isso
criar_arquivo de log
dormir 3
criar_arquivo de log
dormir 3
criar_arquivo de log
dormir 3
criar_arquivo de log
# há algum arquivo para remover?
for ((arquivo=1; arquivo<=$contagem de arquivos; arquivo++))
fazer
#remove o arquivo de log
eval $rm_string ${logfiles[$file]} "excluído..."
arquivos de log[$arquivo]=""
feito
O script declara um array chamado logfiles
. Isso conterá os nomes dos arquivos de log criados pelo script. Ele declara uma variável chamada filecount
. Isso conterá o número de arquivos de log que foram criados.
Ele também declara uma string chamada rm_string
. Em um script do mundo real, isso conteria o rm
comando , mas estamos usandoecho
para demonstrar o princípio de maneira não destrutiva.
A função create_logfile()
é onde cada arquivo de log é nomeado e onde seria aberto. Estamos apenas criando o nome do arquivo e fingindo que ele foi criado no sistema de arquivos.
A função incrementa a filecount
variável. Seu valor inicial é zero, então o primeiro nome de arquivo que criamos é armazenado na posição um do array. Isso é feito de propósito, como veremos mais tarde.
O nome do arquivo é criado usando o date
comando e a extensão “.log”. O nome é armazenado na matriz na posição indicada por filecount
. O nome é impresso na janela do terminal. Em um script do mundo real, você também criaria o arquivo real.
O corpo do script é simulado usando o sleep
comando . Ele cria o primeiro arquivo de log, aguarda três segundos e cria outro. Ele cria quatro arquivos de log, espaçados para que os carimbos de data e hora em seus nomes de arquivos sejam diferentes.
Finalmente, há um loop que exclui os arquivos de log. O arquivo do contador de loop está definido como um. Ele conta até e incluindo o valor de filecount
, que contém o número de arquivos que foram criados.
Se filecount
ainda estiver definido como zero — porque nenhum arquivo de log foi criado — o corpo do loop nunca será executado porque um não é menor ou igual a zero. É por isso que a filecount
variável foi definida como zero quando foi declarada e foi incrementada antes da criação do primeiro arquivo.
Dentro do loop, usamos eval
o nosso não destrutivo rm_string
e o nome do arquivo que é recuperado do array. Em seguida, definimos o elemento do array como uma string vazia.
Isso é o que vemos quando executamos o script.
./clear-logs.sh
Nem tudo é ruim
Muito difamado eval
definitivamente tem sua utilidade. Como a maioria das ferramentas, usada de forma imprudente, é perigosa e em mais de um aspecto.
Se você garantir que as strings nas quais ele funciona sejam criadas internamente e não capturadas de humanos, APIs ou coisas como solicitações HTTPS, você evitará as principais armadilhas.