Comment fonctionne awk '! A [$ 0] ++'?

40

Cette ligne supprime les lignes en double de la saisie de texte sans tri préalable.

Par exemple:

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

Le code original que j'ai trouvé sur les internets se lit comme suit:

awk '!_[$0]++'

Cela me rendait encore plus perplexe alors que je prenais _une signification particulière dans awk, comme dans Perl, mais il s’est avéré qu’il ne s’agissait que du nom d’un tableau.

Maintenant, je comprends la logique qui se cache derrière une ligne: chaque ligne d’entrée est utilisée comme clé dans un tableau de hachage. Ainsi, une fois terminée, la table de hachage contient des lignes uniques dans l’ordre d’arrivée.

Ce que j'aimerais apprendre, c'est à quel point cette notation est interprétée par awk. Par exemple, ce que signifie le signe bang ( !) et les autres éléments de cet extrait de code.

Comment ça marche?

Alexander Shcheblikin
la source
titre est trompeur, il devrait être 0 $ (zéro), pas $ o (o).
Archemar
2
Comme il s'agit d'un hachage, il n'est pas ordonné. Par conséquent, "dans l'ordre d'arrivée" n'est pas correct.
Kevin

Réponses:

35

Voyons voir,

 !a[$0]++

première

 a[$0]

nous regardons la valeur de a[$0](tableau aavec toute la ligne d'entrée ( $0) comme clé).

S'il n'existe pas (la !négation dans test sera-t-elle vraie)

 !a[$0]

nous imprimons la ligne de saisie $0(action par défaut).

De plus, nous ajoutons un ( ++) à a[$0], ainsi la prochaine fois !a[$0]sera évaluée à false.

Nice, trouve !! Vous devriez jeter un coup d'œil au code golf!

Archemar
la source
1
L'essence est la suivante: l'expression entre guillemets simples est utilisée par awkcomme test pour chaque ligne d'entrée; chaque fois que le test réussit, awkl'action est exécutée entre accolades, ce qui, lorsqu'il est omis, l'est {print}. Merci!
Alexander Shcheblikin
3
@ Archemar: Cette réponse est fausse, voyez la mienne.
jeudi
@AlexanderShcheblikin dans awk, l'action par défaut est {print $0}. Cela signifie que tout ce qui est évalué comme vrai l'exécutera par défaut. Ainsi, par exemple, awk '1' fileaffiche toutes les lignes, awk '$1' filetoutes les lignes dont le premier champ n'est pas vide ou 0, etc.
fedorqui
6
@ Gnouc Je ne vois pas d'erreur grave dans cette réponse. Si c'est ce à quoi vous faites référence, l'incrémentation est bien appliquée après le calcul de la valeur de l'expression. Il est vrai que l'incrémentation a lieu avant l'impression, mais c'est une imprécision mineure qui n'affecte pas l'explication de base.
Gilles 'SO- arrête d'être méchant'
1
J'ai trouvé la meilleure explication qu'un débutant puisse comprendre ici à Quora: qr.ae/TUIVxM
GP92
30

Voici le traitement:

  • a[$0]: regardez la valeur de la clé $0, dans un tableau associatif a. S'il n'existe pas, créez-le.

  • a[$0]++: incrémente la valeur de a[$0], renvoie l'ancienne valeur en tant que valeur d'expression. Si a[$0]n'existe pas, le retour 0et l' incrément a[$0]de 1( ++opérateur revient valeur numérique).

  • !a[$0]++: nie la valeur de l'expression. Si a[$0]++return 0, l'expression entière est évaluée à true, awkaction effectuée par défaut print $0. Sinon, l'expression entière est évaluée à false, les causes awkne font rien.

Les références:

Avec gawk, nous pouvons utiliser dgawk (ou awk --debugavec une version plus récente) pour déboguer un gawkscript. Tout d’abord, créez un gawkscript, nommé test.awk:

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

Puis lancez:

dgawk -f test.awk

ou:

gawk --debug -f test.awk

Dans la console du débogueur:

$ 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>

Vous pouvez voir, a Op_postincrementété exécuté avant Op_not.

Vous pouvez également utiliser siou à la stepiplace de sou steppour voir plus clairement:

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
la source
3
@Archemar: Votre réponse indique qu'elle !est appliquée auparavant ++.
jeudi
6
Cette réponse est fausse. L'incrémentation a lieu après le !calcul du résultat de l' opérateur. Vous confondez la priorité des opérateurs ( !a[$0]++est analysée de la même manière !(a[$0]++)) avec l'ordre d'évaluation (l'attribution de la nouvelle valeur de a lieu a[$0]après le calcul de la valeur de l'expression).
Gilles 'SO- arrête d'être méchant'
5
@Gnouc Il est dit dans le passage que vous avez cité, et si cela fonctionnait comme vous l'avez décrit, ce code n'aurait pas l'effet désiré. Tout d'abord, la valeur !xest calculée, où xest l'ancienne valeur de a[$0]. Puis a[$0]est réglé sur 1+x.
Gilles 'SO- arrête d'être méchant'
7
Je pense que votre analyse de ce que fait awk est correcte. Désolé si j'ai impliqué autrement hier. Cependant, votre critique de la réponse d'Archemar est fausse. Archemar ne comprend pas mal la préséance, mais vous confondez la préséance avec l'ordre d'évaluation (voir mon commentaire précédent). Si vous supprimez toute mention de la réponse d'Archemar dans la vôtre, votre réponse devrait être correcte. Dans l’état actuel des choses, il s’efforce de prouver qu’Archemar a tort, et ce n’est pas le cas.
Gilles, arrête de faire le mal le
5
Enfin