- 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
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 semCONCURRENTLY; como instruções SQL podem se estender por várias linhas, um match simples era difícilCom 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
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 apenasCURRIsso usa o hold buffer do sed; com
-n, a saída padrão é suprimida, epimprime só as linhas necessáriasxtroca a linha atual com o hold buffer e, dentro do bloco/PREV/ { ... }, troca de novo e imprime apenas se a linha atual contiverCURRO último
xserve para desfazer a troca em casos de matches sobrepostosClaro 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
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
Há também uma do ano passado: https://news.ycombinator.com/item?id=32467957 (374 pontos, 294 comentários)
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”
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
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
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
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
Um ponto menos conhecido do gawk é que ele normalmente vem com extensões úteis
Dá para acessar recursos como
readdir(),ord(),chr(),gettimeofday()esleep()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
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
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 awkpasso 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 maisPDF/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
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
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
É em Nim, mas talvez isso não seja uma grande barreira, e outras ferramentas dentro de
bu/também podem interessarcut:echo "key: value" | cut -wf 2Mas é discutível se isso é realmente mais simples
Conferindo, o GNU cut não tem
-w, então é exclusivo do BSDNo fim, é melhor usar awk mesmo
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
cutque usaawkpara 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 awkecho "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