Como o awk '! A [$ 0] ++' funciona?

40

Esse alinhamento remove linhas duplicadas da entrada de texto sem pré-classificação.

Por exemplo:

$ cat >f
q
w
e
w
r
$ awk '!a[$0]++' <f
q
w
e
r
$ 

O código original que encontrei nas internets dizia:

awk '!_[$0]++'

Isso foi ainda mais desconcertante para mim, pois eu _tive um significado especial no awk, como no Perl, mas acabou sendo apenas o nome de uma matriz.

Agora, eu entendo a lógica por trás da linha única: cada linha de entrada é usada como chave em uma matriz de hash; assim, após a conclusão, o hash contém linhas únicas na ordem de chegada.

O que eu gostaria de aprender é como exatamente essa notação é interpretada pelo awk. Por exemplo, o que significa o sinal de estrondo ( !) e os outros elementos desse trecho de código.

Como funciona?

Alexander Shcheblikin
fonte
O título é enganoso, deve ser $ 0 (Zero), não $ o (o).
Archemar
2
Como é um hash, não é ordenado; portanto, "na ordem de chegada" não está realmente correto.
7114 Kevin

Respostas:

35

Vamos ver,

 !a[$0]++

primeiro

 a[$0]

olhamos o valor de a[$0](array acom toda a linha de entrada ( $0) como chave).

Se não existir (a !negação no teste será avaliada como verdadeira)

 !a[$0]

imprimimos a linha de entrada $0(ação padrão).

Além disso, adicionamos um ( ++ ) a a[$0], para que da próxima vez o valor !a[$0]seja falso.

Bom, encontre !! Você deve dar uma olhada no código de golfe!

Archemar
fonte
1
Portanto, a essência é esta: a expressão entre aspas simples é usada awkcomo teste para cada linha de entrada; toda vez que o teste é awkexecutado com êxito, a ação é feita com chaves, o que, quando omitido, é {print}. Obrigado!
Alexander Shcheblikin
3
@Archemar: Esta resposta está errada, veja a minha.
amigos estão dizendo sobre cuonglm
@AlexanderShcheblikin em awk, a ação padrão é {print $0}. Isso significa que qualquer coisa avaliada como verdadeira executará isso como padrão. Assim, por exemplo awk '1' fileimprime todas as linhas, awk '$1' fileimprime todas as linhas cujo primeiro campo não está vazio ou 0, etc.
fedorqui
6
@ Gnouc Não vejo nenhum erro grave nesta resposta. Se é a isso que você está se referindo, o incremento é realmente aplicado depois que o valor da expressão é calculado. É verdade que o incremento acontece antes da impressão, mas essa é uma pequena imprecisão que não afeta a explicação básica.
Gilles 'SO- stop be evil'
1
Eu encontrei a melhor explicação para um novato entender aqui em Quora: qr.ae/TUIVxM
GP92
30

Aqui está o processamento:

  • a[$0]: observe o valor da chave $0, no array associativo a. Se não existir, crie-o.

  • a[$0]++: incrementa o valor de a[$0], retorna o valor antigo como valor da expressão. Se a[$0]não existir, retorne 0e aumente a[$0]para 1(o ++operador retorna valor numérico).

  • !a[$0]++: nega o valor da expressão. Se a[$0]++retornar 0, toda a expressão será avaliada como verdadeira, faça a awkação padrão executada print $0. Caso contrário, toda a expressão é avaliada como falsa, as causas awknão fazem nada.

Referências:

Com gawk, podemos usar o dgawk (ou awk --debugcom a versão mais recente) para depurar um gawkscript. Primeiro, crie um gawkscript chamado test.awk:

BEGIN {                                                                         
    a = 0;                                                                      
    !a++;                                                                       
}

Então corra:

dgawk -f test.awk

ou:

gawk --debug -f test.awk

No console do depurador:

$ dgawk -f test.awk
dgawk> trace on
dgawk> watch a
Watchpoint 1: a
dgawk> run
Starting program: 
[     1:0x7fe59154cfe0] Op_rule             : [in_rule = BEGIN] [source_file = test.awk]
[     2:0x7fe59154bf80] Op_push_i           : 0 [PERM|NUMCUR|NUMBER]
[     2:0x7fe59154bf20] Op_store_var        : a [do_reference = FALSE]
[     3:0x7fe59154bf60] Op_push_lhs         : a [do_reference = TRUE]
Stopping in BEGIN ...
Watchpoint 1: a
  Old value: untyped variable
  New value: 0
main() at `test.awk':3
3           !a++;
dgawk> step
[     3:0x7fe59154bfc0] Op_postincrement    : 
[     3:0x7fe59154bf40] Op_not              : 
Watchpoint 1: a
  Old value: 0
  New value: 1
main() at `test.awk':3
3           !a++;
dgawk>

Você pode ver, Op_postincrementfoi executado antes Op_not.

Você também pode usar siou em stepivez de sou steppara ver com mais clareza:

dgawk> si
[     3:0x7ff061ac1fc0] Op_postincrement    : 
3           !a++;
dgawk> si
[     3:0x7ff061ac1f40] Op_not              : 
Watchpoint 1: a
  Old value: 0
  New value: 1
main() at `test.awk':3
3           !a++;
cuonglm
fonte
3
@Archemar: Sua resposta indica que !é aplicada antes ++.
cuonglm
6
Esta resposta está errada. O incremento acontece depois que o resultado do !operador é calculado. Você está confundindo a precedência do operador ( !a[$0]++é analisada como !(a[$0]++)) com a ordem de avaliação (a atribuição do novo valor de a[$0]acontece após o cálculo do valor da expressão).
Gilles 'SO- stop be evil'
5
@Gnouc Diz exatamente na passagem que você citou, e se funcionasse da maneira que você descreveu, esse código não teria o efeito desejado. Primeiro, o valor !xé calculado, onde xestá o valor antigo de a[$0]. Então a[$0]está definido como 1+x.
Gilles 'SO- stop be evil'
7
Acredito que sua análise do que o awk faz está correta. Desculpe se impliquei o contrário ontem. No entanto, sua crítica à resposta de Archemar está errada. Archemar não entende mal de precedência, você está confundindo precedência com ordem de avaliação (veja meu comentário anterior). Se você remover qualquer menção da resposta da Archemar na sua, sua resposta deve estar correta. Como é, está focado em provar que Archemar está errado, e esse não é o caso.
Gilles 'SO- stop being evil' em
5
Bem, pelo menos agora eu sei sobre depurador do awk ...
Archemar