Shell GNU: “grep”, não olhe para trás!
Na noite passada, um membro do grupo "Curso GNU", no Telegram, trouxe um problema muito interessante e comum que ele, em princípio, pensou em resolver com a ferramenta grep
. Mas, como eu sempre digo, "a ferramenta é a última coisa em que nós pensamos quando queremos resolver algo no shell", ou seja, a nossa primeira preocupação deve ser sempre descrever com clareza o problema -- então, vamos lá!
Dada a saída de um comando que fornece várias linhas no formato de um parchave/valor
, é preciso localizar a linha iniciada com uma determinada palavra e, desta linha, obter apenas a última palavra.
Para efeito de demonstração, vamos substituir o comando pelo conteúdo de um arquivo contendo linhas no mesmo formato:
:~$ cat exemplo.txt
Server ID: 123
Host: 127.0.0.1
Port: 7999
Username: fulano
Password: abcdefghij
Database: funalo_db
Assim, a nossa meta será obter apenas a senha da linha iniciada com Password:
.
Tratando-se daquilo que nós podemos chamar de dados tabulares, o meu primeiro impulso seria utilizar o interpretador da linguagem awk
:
:~$ awk '/Password/ {print $2}' exemplo.txt
abcdefghij
Aqui, eu especifico uma regra na forma de uma expressão regular muito simples que causará o processamento apenas das linhas que contiverem o padrão Password
. Encontrada uma linha correspondendo à minha regra, eu mando meu programa exibir apenas o dado que estiver no segundo campo ($2
).
Para o
awk
os campos de uma linha são, por padrão, quaisquer sequências contínuas de caracteres separadas por um ou mais espaços. Você encontra mais informações sobre a programação em AWK no nosso curso livre e gratuito.
O editor de fluxos de texto sed
também é outra ferramenta muito utilizada para a solução deste tipo de problema. Eu costumo evitar o seu uso em função da sua escrita quase sempre esotérica, no sentido de que, geralmente, a expressão da solução na forma de um comando só pode ser entendida por "iniciados", mas é inegável o poder desse editor de linhas e é altamente recomendável o seu estudo!
Novamente, o padrão da linha terá que ser informado como uma regex, só que de forma mais detalhada. A questão aqui, é que o sed
será utilizado para encontrar e editar a linha que corresponda ao padrão, quer dizer: nós buscaremos a linha e removeremos dela tudo que não for a informação desejada.
Esta será a expressão de uma das possíveis soluções com o sed
:
:~$ sed -n 's/Password: \+//p' tmp/teste-grep.txt
abcdefghij
Decifrando o comando para "não iniciados", o sed
percorrerá silenciosamente (-n
) todas as linhas do arquivo e processará uma substituição apenas daquelas que corresponderem ao padrão buscado, conforme a sintaxe s/busca/substituição/
. O p
, no final da sintaxe de busca e substituição, é um comando para que o resultado do processamento da linha seja exibido na saída. O esquema geral ficaria assim:
+----- Opção '-n' para que nenhuma linha
| seja exibida na saída.
|
| REGEX do padrão buscado
| |
| | Substituir por "nada"
↓ ↓ ↓
:~$ sed -n 's/Password: \+//p' tmp/teste-grep.txt
↑ ↑
substitute print (exibe a linha processada)
Observe que, na expressão regular da busca, nós "escapamos" o caractere +
para que o sed
o entendesse como um metacaractere quantificador de uma regex, e não como um caractere de texto comum. Isso pode ser evitado com o uso da opção -E
, que informa ao sed
que, por padrão, os caracteres devem ser interpretados como metacaracteres de expressões regulares estendidas (ERE).
No final, o resultado é o mesmo:
:~$ sed -nE 's/Password: +//p' tmp/teste-grep.txt
abcdefghij
Pela sua simplicidade e flexibilidade, o grep
é a ferramenta favorita dos administradores de sistemas e utilizadores do sistema operacional GNU quando o assunto é filtrar saídas de dados. Porém, a tal da "praticidade" pode levar à acomodação nos usos triviais e a soluções desnecessariamente verbosas como esta:
:~$ grep Password: tmp/teste-grep.txt | tr -s ' ' | cut -d' ' -f2
abcdefghij
Aqui, o grep
foi utilizado apenas para obter a linha buscada, deixando o processamento para dois outros utilitários encadeados por pipes:
-
tr -s ' '
: substitui todas as ocorrências duplicadas do caractere espaço por apenas um espaço. -
cut -d' ' -f2
: quebra a linha em campos na ocorrência do caractere espaço e exibe apenas o segundo campo.
O problema aqui é que são três problemas:
- Nós utilizamos 3 programas (e dois subshells) para fazer o trabalho de apenas um programa (
awk
oused
, por exemplo); - O
grep
também daria conta do recado sozinho; - Mas nós só estamos fugindo do estudo das expressões regulares.
O que há de mais interessante no grep
, pelo menos na minha opinião, é o fato dele nos permitir trabalhar com vários tipos de padrões de regex. Um desses padrões é o modo de compatibilidade com o Perl, ou PCRE. Sem entrar em muitos detalhes, o modo PCRE permite o uso de alguns operadores muito poderosos nas nossas expressões regulares, entre eles, o \K
(variable-lenght look behind).
Apesar do nome complicado em inglês, o que este operador faz é algo muito simples: tendo o ponto onde ele aparece na regex como referência, o que está antes dele é levado em conta no casamento, mas o resultado destacará apenas o que vier depois dele.
Observe a diferença dos resultados (o padrão casado será representado entre colchetes):
:~$ # Utilizando `-E` para escrevermos regex'es estendidas (ERE)...
:~$ grep -E 'Password:\s+.*' tmp/teste-grep.txt
[Password: abcdefghij]
:~$ # Utilizando `-P` para escrevermos regex'es PCRE sem o `\K`...
:~$ grep -P 'Password:\s+.*' tmp/teste-grep.txt
[Password: abcdefghij]
:~$ # PCRE com o operador `\K`...
:~$ grep -P 'Password:\s+\K.*' tmp/teste-grep.txt
Password: [abcdefghij]
Muito bom, mas o trabalho não está terminado! O grep
, no modo PCRE, foi capaz de encontrar a informação que nós queríamos, mas ele apenas marcou o resultado do casamento do padrão. Para nossa sorte, o grep
tem outro recurso bem conhecido, a opção -o
(only), que exibe na saída apenas o resultado do casamento.
Sendo assim, aqui está uma outra possível solução apenas com o utilitário grep
:
:~$ grep -oP 'Password:\s+\K.*' tmp/teste-grep.txt
abcdefghij
O sistema operacional GNU tem um conjunto de ferramentas poderosíssimo que merece e precisa ser estudado por aqueles que realmente buscam autonomia e um total domínio sobre a sua computação. Mas existem coisas cujas utilidades vão além das ferramentas do sistema, e as expressões regulares entram nessa categoria, tanto que todas as soluções propostas aqui, exceto a mais preguiçosa, envolvem o uso de expressões regulares com diferentes graus de complexidade.
No momento em que esse artigo está sendo escrito, nós ainda não temos um curso livre e gratuito sobre regex aqui na comunidade debxp, mas isso está sendo providenciado com a contribuição dos nossos colaboradores. Se você quiser contribuir com mais este projeto e aprender regex conosco, as informações estão em https://blauaraujo.com/regex.
Bons estudos! 🙂