4 pontos por GN⁺ 2023-08-29 | 1 comentários | Compartilhar no WhatsApp
  • O GNU awk percorre automaticamente a stdin e arquivos, combinando filtragem, substituição e processamento de campos em comandos de uma linha para lidar com tarefas de texto na CLI
  • /regexp/ e !/regexp/ são sintaxes abreviadas que verificam a linha de entrada atual e, quando a condição é verdadeira, $0 é impresso por padrão
  • sub substitui apenas a primeira ocorrência, enquanto gsub substitui todas; se o alvo for omitido, a substituição é aplicada à linha de entrada atual $0
  • Graças à separação de campos por espaços e a $N, NF e $NF, o awk é muito adequado para trabalhos baseados em campos, como selecionar colunas específicas ou processá-las com condições
  • Ao encadear trechos cond{action} e adicionar BEGIN{} e END{}, é possível criar pequenos programas que vão além de filtros simples, incluindo strings, números e arrays associativos

Como o awk funciona

  • O awk pode filtrar texto como grep e sed, e também oferece recursos de programação para processamentos mais complexos
  • A entrada pode vir da stdin ou de arquivos e, por padrão, é percorrida linha por linha, aplicando condições e ações
  • A linha inteira de entrada pode ser acessada pela variável especial $0
    • O termo exato é record de entrada, mas neste capítulo a explicação será em nível de linhas

Filtragem e saída padrão

  • Expressões regulares são escritas no formato /.../, e a sintaxe string ~ /regexp/ é usada para verificar se uma string corresponde à regex
  • A condição inversa é expressa com string !~ /regexp/
  • Se a string a ser verificada for omitida, o alvo passa a ser $0
  • Se houver apenas uma condição e nenhuma ação, quando a condição for verdadeira, $0 será impresso automaticamente
    • awk '/regexp/' é a forma abreviada de awk '$0 ~ /regexp/{print $0}'
    • awk '!/regexp/' é a forma abreviada de awk '$0 !~ /regexp/{print $0}'
  • Nos exemplos, são mostradas a impressão apenas das linhas que contêm at ou das linhas que não contêm e, ilustrando o fluxo básico de filtragem

A saída idiomática com 1

  • Em expressões condicionais, números diferentes de zero e strings não vazias são avaliados como verdadeiros
  • awk '1' é uma forma idiomática de fornecer uma condição que é sempre verdadeira para todas as linhas de entrada
  • Quando não há ação, uma condição verdadeira resulta na impressão de $0, então awk '1' imprime toda a entrada como está
  • O resultado é igual a awk '{print $0}' ou simplesmente cat

Substituição: sub e gsub

  • O awk fornece as funções sub e gsub para busca e substituição
  • sub(/:/, "-") troca apenas o primeiro : por - em cada linha de entrada
  • gsub(/:/, "-") troca todos os : por - em cada linha de entrada
  • As duas funções recebem como primeiro argumento a regex a ser correspondida e como segundo a string de substituição
  • Se a string de entrada não for fornecida separadamente, o alvo padrão é $0
  • Quando a substituição é bem-sucedida, a entrada-alvo é modificada
  • O 1 após o bloco de substituição é interpretado como uma expressão condicional fora do bloco, fazendo com que o $0 modificado seja impresso novamente
    • awk '{sub(/:/, "-")} 1' produz o mesmo resultado que awk '{sub(/:/, "-"); print $0}'
    • Mesmo usando apenas print, o alvo padrão da saída continua sendo $0

Como escolher entre grep, sed e awk

  • Para filtragem simples de linhas, o grep, otimizado para esse propósito, pode ser mais rápido que sed ou awk
  • Para tarefas de substituição, o sed pode ser mais rápido que awk
  • As funcionalidades entre as ferramentas nem sempre correspondem 1:1
    • Para implementar grep -o com sed ou awk, são necessárias mais etapas
    • Busca recursiva é algo que só o grep oferece
  • Uma discussão relacionada pode ser vista em unix.stackexchange: When to use grep, sed, awk, perl, etc

Processamento baseado em campos

  • O awk é usado com muita frequência especialmente para processamento baseado em campos
  • Por padrão, ele divide a linha de entrada com base em espaços em branco, e cada campo pode ser acessado com $N
    • $2 é o segundo campo
    • $NF é o último campo
    • NF é o número total de campos na linha de entrada atual
  • O exemplo table.txt mostra de forma compacta o processamento por colunas
    • imprimir o segundo campo de cada linha
    • imprimir apenas as linhas cujo último campo é negativo
    • alterar b para B apenas no primeiro campo
  • O arquivo de exemplo está no diretório example_files

Estrutura de um comando de uma linha

  • Um comando típico de uma linha em awk tem a seguinte forma
awk 'cond1{action1} cond2{action2} ... condN{actionN}'
  • Se não houver condição, a ação será executada sempre
  • Se não houver ação, quando a condição for verdadeira, $0 será impresso
  • Dentro de um bloco, várias instruções podem ser separadas por ponto e vírgula ;
  • Ao encadear vários blocos, na maioria dos comandos de uma linha é possível expressar ações condicionais sem if explícito
    • awk '$NF<0' table.txt imprime apenas as linhas cujo último campo é negativo
  • BEGIN{} é executado antes da leitura da entrada
  • END{} é executado depois que todo o processamento da entrada termina
  • Mais detalhes sobre operadores e expressões condicionais estão em gawk manual: Operators, gawk manual: Truth Values and Conditions

Strings e números

  • O awk determina o tipo dos valores conforme o contexto para permitir soluções curtas na CLI
  • Literais de string são escritos entre aspas duplas
  • Números podem usar inteiros, ponto flutuante e notação científica
  • BEGIN{} também pode ser usado quando se executa um programa awk sem entrada externa
  • Variáveis podem armazenar números e strings
    • Exemplo: a=5; b=2.5; print a+b
    • Ao colocar strings lado a lado, elas são concatenadas
  • Variáveis não inicializadas se comportam como string vazia em contexto de string e como 0 em contexto numérico
  • Quando uma string é usada em uma expressão numérica, ela é convertida à força para número
    • Se a string não começar com um número válido, ela será tratada como 0
    • Espaços em branco no início são ignorados
  • Ao concatenar uma string a um número, o número é convertido em string
  • Mais detalhes estão em gawk manual: Constant Expressions e gawk manual: How awk Converts Between Strings and Numbers

Arrays

  • Os arrays de awk são arrays associativos e funcionam como pares chave-valor
  • As chaves podem ser números ou strings, mas chaves numéricas são convertidas internamente em strings
  • Também é possível usar arrays multidimensionais
  • Nos exemplos, valores são armazenados e acessados com chaves de string como student["id"] e student["name"]
  • A existência de uma chave pode ser verificada no formato "id" in student
  • Mais detalhes estão em gawk manual: Arrays

Prática e próximos passos

  • Este capítulo apresenta brevemente as abreviações básicas da sintaxe de awk, filtragem, substituição, processamento de campos, conversão de tipos e arrays
  • O próximo capítulo trata de expressões regulares, e os recursos introduzidos aqui continuarão sendo usados nos exemplos
  • Você pode se familiarizar com a sintaxe mudando os números dos campos ou testando outras entradas, como campos negativos e de ponto flutuante
  • O app TUI para prática interativa pode ser instalado a partir do repositório AwkExercises, e o modo de uso está em app_guide.md
  • Todos os exercícios estão reunidos em Exercises.md, e as soluções estão em Exercise_solutions.md

1 comentários

 
GN⁺ 2023-08-29
Opiniões no Hacker News
  • Gosto de awk e uso com bastante frequência; um dos principais usos é como um sed com estado
    Por exemplo, para fazer match só quando uma linha aparece depois de uma linha específica anterior, o que é útil ao criar linters temporários
    Recentemente criei uma verificação para encontrar arquivos de migração em que um CREATE INDEX, potencialmente problemático em tabelas muito grandes, aparecia sem CONCURRENTLY; como instruções SQL podem se estender por várias linhas, um match simples era difícil
    Com awk dá para fazer rastreamento de estado ao longo de várias linhas, como “estou dentro de uma instrução create” ou “estou criando um índice”, então um script meio improvisado vem funcionando bem há cerca de um ano

    • Um dia ainda preciso aprender awk, mas enquanto isso aprendi recursos baseados em estado mais profundos do sed
      Para imprimir a linha atual apenas quando a linha anterior corresponder a um determinado padrão, dá para fazer algo como sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}'
      Ex.: echo -e "PREV\nCURR\nCURR\nCURR\nPREV\nRED" | sed -ne 'x' -e '/PREV/ {x; /CURR/ p; x}' imprime apenas CURR
      Isso usa o hold buffer do sed; com -n, a saída padrão é suprimida, e p imprime só as linhas necessárias
      x troca a linha atual com o hold buffer e, dentro do bloco /PREV/ { ... }, troca de novo e imprime apenas se a linha atual contiver CURR
      O último x serve para desfazer a troca em casos de matches sobrepostos
      Claro que um script awk provavelmente seria muito mais simples e direto, mas também dá para fazer isso com sed
      Talvez o tempo gasto aprendendo isso tivesse sido melhor usado em awk, mas o tutorial https://www.grymoire.com/Unix/Sed.html é tão bom que aprendi nele quase tudo que sei sobre sed
    • Seria legal se você pudesse compartilhar um exemplo de como rastreou o estado do SQL com awk
  • Quem está lendo esta thread talvez também se interesse pelo “Ask HN: Share a shell script you like”, de duas semanas atrás
    Com 78 comentários, não bombou tanto quanto eu esperava, mas vale como referência: https://news.ycombinator.com/item?id=37112991

  • Mantenho um pequeno interesse por Awk, junto com Lisp e afins, e em 2022 criei o cppawk: https://www.kylheku.com/cgit/cppawk/about/
    O cppawk estende o Awk com recursos de pré-processamento
    Ele tem macros de loop com suporte a várias cláusulas; as cláusulas podem ser combinadas para criar iteração paralela ou produto cartesiano, e também podem ser estendidas pelo usuário
    Com 5 macros simples, dá para definir uma nova cláusula
    Se você usa Awk, pode ser útil; ele é documentado em várias man pages e tem testes unitários que rodam no gawk e no mawk

  • Talvez isso revele minha sensibilidade de administrador de sistemas das antigas, mas não vejo bem qual é a vantagem do awk em relação a simplesmente escrever a mesma coisa em Perl
    Já vi muitos scripts shell horríveis feitos por administradores de sistemas juniores e, toda vez, pensei: “a parte de processamento de texto teria ficado muito mais limpa em Perl”

    • Se você está em um ambiente em que Perl está sempre disponível, isso faz bastante sentido
      Para mim, awk é uma das poucas linguagens que posso deixar de lado por meses e recuperar o jeito em 10 minutos
      Ele tem um lado intuitivo e se encaixa naturalmente com ferramentas comuns de linha de comando
    • Se a comparação entre Awk e Perl for para scripts, prefiro Perl ou Python
      Mas este texto trata de comandos de uma linha curtos e temporários, e nesses casos sed/awk são melhores que Perl
      Se você já sabe Perl, pode continuar usando Perl sem precisar aprender mais uma ferramenta
    • A força do Awk é o loop de leitura, a separação de campos e o modelo de correspondência por padrões/condições que vêm de graça
      Como linguagem em si, é “ok”, mas suficientemente utilizável
      Claro que dá para fazer tudo isso em Perl, mas você precisa escrever manualmente o boilerplate que o awk entrega de graça
      As vantagens da linguagem Perl não são grandes o bastante para eu abandonar awk, e como uso awk mais para processamento de dados e manipulação pontual de arquivos do que para “scripting”, não sinto falta da profundidade do Perl
      Se preciso de recursos mais profundos, vou para outro lugar
    • Não há exatamente uma vantagem técnica; os recursos do Perl são deliberadamente próximos de um superconjunto do awk
      Como a geração atual não aprendeu Perl, parece estar redescobrindo a ideia de processamento de streams por meio do awk
      O awk foi uma ótima ideia para o fim dos anos 1970 e uma inovação real que merecia ser imitada
      Depois, o Perl a imitou e a superou, mas foi esquecido; por isso é meio constrangedor ver o awk sendo descoberto de novo
    • Sem ironia: tenho curiosidade de saber por que Perl seria melhor que Python para scripts
  • Um ponto menos conhecido do gawk é que ele normalmente vem com extensões úteis
    Dá para acessar recursos como readdir(), ord(), chr(), gettimeofday() e sleep()
    https://www.gnu.org/software/gawk/manual/html_node/Extension-Samples.html

  • Comandos de uma linha em awk são realmente poderosos
    A pergunta difícil é se vale a pena investir em programação awk mais complexa
    Quando uma tarefa de processamento exige lógica complicada, o awk até oferece isso, mas no estilo curto e obscuro dos primórdios da computação
    Por outro lado, escolher uma alternativa moderna também tem seu peso, pode não ser intuitiva — especialmente algo como pandas — e ainda pode trazer problemas de desempenho

    • Para mim, o grande problema são as bibliotecas
      Nem mesmo arquivos pessoais de funções comuns parecem ser bem suportados, e também não parece haver uma boa forma de trazer bibliotecas de terceiros
      Quando começo a precisar de funções auxiliares e a dividir em várias linhas, geralmente passo para Python
      Aí o programa fica 2 a 3 vezes maior e eu solto um suspiro
      Ruby é uma ótima alternativa ao awk, mas, se os colegas de trabalho não conhecem Ruby, fica difícil esperar manutenção
    • Antes do ChatGPT, eu não gostava de comandos de uma linha obscuros em awk/sed
      Hoje em dia ficou mais aceitável, porque ao colar esses comandos difíceis de ler em uma IA ela explica passo a passo muito bem
      Ainda prefiro algumas linhas de Python pela possibilidade de testes unitários, mas, para trabalhos rápidos de manipulação de dados, muitas vezes nem há necessidade de testes unitários
  • Foi lançada uma nova versão do e-book “CLI text processing with GNU awk
    É possível aprender o comando GNU awk passo a passo, do básico ao avançado, com centenas de exemplos e exercícios; o material aprofunda processamento de campos, filtragem, processamento de múltiplos arquivos, soluções que dependem de vários registros, comparação de registros e campos entre arquivos, busca de duplicados preservando a ordem de entrada, expressões regulares e mais
    PDF/EPUB: https://learnbyexample.gumroad.com/l/gnu_awk (gratuito até 31 de agosto de 2023)
    Versão web: https://learnbyexample.github.io/learn_gnuawk/
    Código-fonte em Markdown e arquivos de exemplo: https://github.com/learnbyexample/learn_gnuawk
    App TUI interativo para praticar: https://github.com/learnbyexample/TUI-apps/blob/main/AwkExercises
    Como pacote, o conjunto Magical one-liners de comandos de uma linha em grep, sed, awk, perl e ruby sai por US$ 5: https://learnbyexample.gumroad.com/l/oneliners/new_awk_release, e o pacote completo de 13 e-books sai por US$ 12: https://learnbyexample.gumroad.com/l/all-books/new_awk_release
    Qualquer feedback — erros de digitação, erros de código, partes que funcionaram bem ou não — ajuda muito
    Discussões anteriores incluem https://news.ycombinator.com/item?id=15549318 e https://news.ycombinator.com/item?id=22758217

    • Fico curioso para saber quantas pessoas realmente pagam por materiais como Magical one-liners, e se você já experimentou um modelo de “ver primeiro e escolher pagar depois”
      Pode ser útil a ponto de valer US$ 5 ou US$ 15, mas também pode não ser diferente do que já guardei no meu arquivo pessoal de one-liners, então hesito em pagar só para descobrir
    • Dá para ver claramente que houve muito trabalho envolvido, e gostei especialmente da aplicação TUI
      Fico curioso para saber se a versão web continuará gratuita depois de 31 de agosto
  • Uma vez fiz um golf em desenhar um coração com AWK: https://gist.github.com/auselen/906a53b47a7d616b080dbef85eb8f776

  • 99,9% do uso de awk é para dividir linhas descartando espaços em branco consecutivos
    Ex.: echo "key: value" | awk '{print $1}'
    Seria bom ter uma alternativa mais simples

    • Vale considerar https://github.com/c-blake/bu/blob/main/doc/cols.md
      É em Nim, mas talvez isso não seja uma grande barreira, e outras ferramentas dentro de bu/ também podem interessar
    • Também dá para fazer com cut: echo "key: value" | cut -wf 2
      Mas é discutível se isso é realmente mais simples
      Conferindo, o GNU cut não tem -w, então é exclusivo do BSD
      No fim, é melhor usar awk mesmo
    • Também vale conferir https://github.com/sstadick/hck e https://github.com/theryangeary/choose
      Ambos são alternativas a cut/awk e também suportam separação baseada em regex, mas, se me lembro bem, não removem espaços em branco do início/fim
      O meu https://github.com/learnbyexample/regexp-cut é uma ferramenta parecida com cut que usa awk para oferecer separação baseada em regex, índices negativos etc., e também lida com espaços em branco no início/fim graças ao comportamento padrão do awk
    • Provavelmente você queria dizer echo "key: value" | awk '{print $2}'
  • Há um tempo, reescrevi em awk um script diff2html que eu tinha portado para bash, e, por motivos óbvios, ficou muito mais rápido
    O awk também ficou muito mais fácil de ler do que o script em bash, e consegui aprender a linguagem, depurar e entender os bugs para corrigi-los em uma única noite
    Não sei se era a forma idiomática de escrever em awk, mas achei uma linguagem realmente boa
    https://github.com/berry-thawson/diff2html/blob/master/diff2html.sh