Como usar eval em scripts Bash do Linux
Publicados: 2022-08-22 De todos os comandos do Bash, o pobre e velho eval
provavelmente tem a pior reputação. Justificado, ou apenas má imprensa? Discutimos o uso e os perigos desse menos amado dos comandos do Linux.
Precisamos Falar Sobre Aval
Usado de forma descuidada, o eval
pode levar a um comportamento imprevisível e até mesmo a inseguranças do sistema. Pelos sons, provavelmente não deveríamos usá-lo, certo? Bem, não exatamente.
Você poderia dizer algo semelhante sobre automóveis. Nas mãos erradas, eles são uma arma mortal. As pessoas os usam em ataques de aríete e como veículos de fuga. Devemos todos parar de usar carros? Não, claro que não. Mas eles têm que ser usados corretamente e por pessoas que sabem como conduzi-los.
O adjetivo usual aplicado a eval
é “mal”. Mas tudo se resume a como ele está sendo usado. O comando eval
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 de seu script.
Os problemas surgem quando um script é escrito para usar eval
em uma string que foi recebida de algum lugar fora do script. Ele 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 string na qual eval
vai funcionar não foi derivada local e programaticamente, existe o risco de que a string possa conter instruções maliciosas incorporadas ou outra entrada mal formada. Obviamente, você não quer que o eval
execute comandos maliciosos. Portanto, para estar seguro, não use eval
com strings geradas externamente ou entrada do usuário.
Primeiros passos com eval
O comando eval
é 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 elementos concatenados. Ele avalia os argumentos e, em seguida, 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 o valor da variável.
eval " $wordcount "
O comando é executado no shell atual, não em um subshell. Podemos mostrar isso facilmente. Temos um pequeno arquivo de texto chamado “variables.txt”. Ele contém essas duas linhas.
primeiro=Como fazer segundo=Geek
Usaremos cat
para enviar essas linhas para a janela do terminal. Em seguida, usaremos eval
para avaliar um comando cat
para que as instruções dentro do arquivo de texto sejam executadas. Isso definirá as variáveis para nós.
cat variáveis.txt eval "$(cat variables.txt)" echo $primeiro $segundo
Usando echo
para imprimir os valores das variáveis, podemos ver que o comando eval
é executado no shell atual, não em um subshell.
Um processo em um subshell não pode alterar o ambiente de shell do pai. Como eval é executado no shell atual, as variáveis definidas por eval
podem ser usadas no shell que iniciou o comando eval
.
Observe que se você usar eval
em um script, o shell que seria alterado por eval
é o subshell no qual o script está sendo executado, não o shell que o iniciou.
RELACIONADO: Como usar os comandos cat e tac do Linux
Usando variáveis na string de comando
Podemos incluir outras variáveis nas strings de comando. Vamos definir duas variáveis para armazenar números inteiros.
num1=10 num2=7
Criaremos uma variável para armazenar um comando expr
que retornará a soma de dois números. Isso significa que precisamos acessar os valores das duas variáveis inteiras no comando. Observe os backticks em torno da instrução expr
.
add="`expr $num1 + $num2`"
Criaremos outro comando para nos mostrar o resultado da instrução expr
.
show="eco"
Observe que não precisamos incluir um espaço no final da string echo
, nem no início da string expr
. eval
cuida disso.
E para executar todo o comando usamos:
eval $show $add
Os valores das variáveis dentro da string expr
são substituídos na string por eval
, antes de ser passado para o shell para ser executado.
RELACIONADO: Como trabalhar com variáveis no Bash
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 mantido na primeira variável, a partir de seu nome que é o valor armazenado na segunda variável. Um exemplo irá ajudá-lo a desembaraçar isso.
Copie este script para um editor e salve-o como um arquivo chamado “assign.sh”.
#!/bin/bash title="Como Fazer Geek" página da web=título comando="eco" eval $command \${$webpage}
Precisamos torná-lo executável com o comando chmod
.
chmod +x assign.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 comando eval
esteja usando a variável webpage
.
./assign.sh
O cifrão de escape “ $
” e as chaves “ {}
” fazem com que eval veja o valor contido na variável cujo nome está armazenado na variável da webpage
da web.
Usando variáveis criadas dinamicamente
Podemos usar eval
para criar variáveis dinamicamente. Esse script é chamado de “loop.sh”.
#!/bin/bash total=0 label="Loop completo. Total:" para n em {1..10} Faz valor x$n=$n echo "Loop" $x$n ((total+=$x$n)) feito eco $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10 echo $label $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 seqüência simples de texto.
Vamos fazer um loop 10 vezes e criar 10 variáveis chamadas x1
até x10
. A instrução eval
no corpo do loop fornece o “x” e recebe o valor do contador de 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 variável total
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 por seus nomes reais, sem usar uma versão calculada ou derivada de seus nomes.
Por fim, imprimimos o valor da variável total
.
./loop.sh
RELACIONADO: Primer: Bash Loops: for, while e until
Usando eval com arrays
Imagine um cenário em que você tem um script de longa duração e executando 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 termina, se não houver erros, ele exclui os arquivos de log que criou.
Você não quer que ele 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/bash declare -a arquivos de log contagem de arquivos=0 rm_string="eco" function create_logfile() { ((++contagem de arquivos)) nome do arquivo=$(data +"%Y-%m-%d_%H-%M-%S").log logfiles[$filecount]=$filename echo $filecount "Criado" ${logfiles[$filecount]} } # corpo do script. Algum processamento é feito aqui que # gera periodicamente um arquivo de log. Vamos simular isso create_logfile dormir 3 create_logfile dormir 3 create_logfile dormir 3 create_logfile # existem arquivos para remover? for ((arquivo=1; arquivo<=$arquivoconta; arquivo++)) Faz # remove o arquivo de log eval $rm_string ${logfiles[$file]} "excluído..." arquivos de log[$file]="" 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 comando rm
, mas estamos usando echo
para que possamos demonstrar o princípio de maneira não destrutiva.
A função create_logfile()
é onde cada arquivo de log é nomeado e onde ele seria aberto. Estamos apenas criando o nome do arquivo e fingindo que ele foi criado no sistema de arquivos.
A função incrementa a variável filecount
. Seu valor inicial é zero, então o primeiro nome de arquivo que criamos é armazenado na posição um na matriz. Isso é feito de propósito, como veremos mais adiante.
O nome do arquivo é criado usando o comando date
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 comando sleep
. 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 arquivo sejam diferentes.
Por fim, há um loop que exclui os arquivos de log. O arquivo do contador de loops é 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 variável filecount
foi definida como zero quando foi declarada e porque foi incrementada antes da criação do primeiro arquivo.
Dentro do loop, usamos eval
com nossa rm_string
não destrutiva 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
O eval
muito difamado definitivamente tem seus usos. Como a maioria das ferramentas, usada de forma imprudente, é perigosa e em mais de uma maneira.
Se você se certificar de que as strings nas quais ele funciona são criadas internamente e não capturadas de humanos, APIs ou coisas como solicitações HTTPS, você evitará as principais armadilhas.
RELACIONADO: Como exibir a data e hora no terminal Linux (e usá-lo em scripts Bash)