Shell GNU: “grep”, não olhe para trás!

Shell: 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á!

O problema

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:.

Solução com AWK

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.

Solução com SED

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

A solução com o GREP

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 ou sed, 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

Conclusão

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! 🙂

Músico, programador, designer e apaixonado pelos ideais do Software Livre.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post comment