¿Cómo funciona awk '! A [$ 0] ++'?

40

Esta línea única elimina las líneas duplicadas de la entrada de texto sin ordenarlas previamente.

Por ejemplo:

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

El código original que he encontrado en Internet dice:

awk '!_[$0]++'

Esto fue aún más desconcertante para mí, ya que asumí _que tenía un significado especial en awk, como en Perl, pero resultó ser solo el nombre de una matriz.

Ahora, entiendo la lógica detrás de una línea: cada línea de entrada se usa como una clave en una matriz de hash, por lo tanto, al finalizar, el hash contiene líneas únicas en el orden de llegada.

Lo que me gustaría aprender es cómo awk interpreta exactamente esta notación. Por ejemplo, qué significa el signo de explosión ( !) y los otros elementos de este fragmento de código.

¿Como funciona?

Alexander Shcheblikin
fuente
el título es engañoso, debería ser $ 0 (cero), no $ o (o).
Archemar
2
Como es un hash, no está ordenado, por lo que "en el orden de llegada" no es realmente correcto.
Kevin

Respuestas:

35

Veamos,

 !a[$0]++

primero

 a[$0]

nos fijamos en el valor de a[$0](matriz acon toda la línea de entrada ( $0) como clave).

Si no existe (si la !negación en la prueba se evaluará como verdadera)

 !a[$0]

imprimimos la línea de entrada $0(acción predeterminada).

Además, agregamos uno ( ++) a a[$0], por lo que la próxima vez !a[$0]se evaluará como falso.

Bien, encuentra !! ¡Deberías echar un vistazo al código golf!

Archemar
fuente
1
Entonces, la esencia es esta: la expresión en las comillas simples se usa awkcomo una prueba para cada línea de entrada; cada vez que la prueba tiene éxito, awkejecuta la acción entre llaves, que, cuando se omite, es {print}. ¡Gracias!
Alexander Shcheblikin
3
@Archemar: Esta respuesta es incorrecta, mira la mía.
Cuonglm
@AlexanderShcheblikin en awk, la acción predeterminada es {print $0}. Esto significa que todo lo evaluado como verdadero ejecutará esto como predeterminado. Así, por ejemplo awk '1' fileimprime todas las líneas, awk '$1' fileimprime todas esas líneas cuyo primer campo no está vacío o 0, etc.
fedorqui
66
@Gnouc No veo ningún error grave en esta respuesta. Si a eso se refiere, el incremento se aplica después de calcular el valor de la expresión. Es cierto que el incremento ocurre antes de la impresión, pero esa es una imprecisión menor que no afecta la explicación básica.
Gilles 'SO- deja de ser malvado'
1
Encontré la mejor explicación para que un novato comprenda aquí en quora: qr.ae/TUIVxM
GP92
30

Aquí está el procesamiento:

  • a[$0]: mira el valor de la clave $0, en una matriz asociativa a. Si no existe, créelo.

  • a[$0]++: incrementa el valor de a[$0], devuelve el valor anterior como valor de expresión. Si a[$0]no existe, devuelva 0e incremente a[$0]a 1(el ++operador devuelve un valor numérico).

  • !a[$0]++: niega el valor de la expresión. Si a[$0]++devuelve 0, toda la expresión se evalúa como verdadera, realice la awkacción predeterminada realizada print $0. De lo contrario, toda la expresión se evalúa como falsa, las causas awkno hacen nada.

Referencias

Con gawk, podemos usar dgawk (o awk --debugcon una versión más nueva) para depurar un gawkscript. Primero, cree un gawkscript, llamado test.awk:

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

Entonces corre:

dgawk -f test.awk

o:

gawk --debug -f test.awk

En la consola del 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>

Puedes ver, Op_postincrementfue ejecutado antes Op_not.

También puede usar sio en stepilugar de so steppara ver más claramente:

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
fuente
3
@Archemar: Su respuesta indica que !se aplica antes ++.
Cuonglm
66
Esta respuesta es incorrecta. El incremento ocurre después de !que se calcula el resultado del operador. Está confundiendo la precedencia del operador ( !a[$0]++se analiza como !(a[$0]++)) con el orden de evaluación (la asignación del nuevo valor de a[$0]ocurre después de que se haya calculado el valor de la expresión).
Gilles 'SO- deja de ser malvado'
55
@Gnouc Dice justo en el pasaje que citó, y si funcionó de la manera que describió, este código no tendría el efecto deseado. Primero !xse calcula el valor , donde xes el valor anterior de a[$0]. Luego a[$0]se establece en 1+x.
Gilles 'SO- deja de ser malvado'
77
Creo que su análisis de lo que hace awk es correcto. Lo siento si implicaba lo contrario ayer. Sin embargo, su crítica de la respuesta de Archemar es incorrecta. Archemar no malinterpreta la precedencia, sí, confunde la precedencia con el orden de evaluación (vea mi comentario anterior). Si elimina cualquier mención de la respuesta de Archemar en la suya, su respuesta debe ser correcta. Tal como está, se centra en demostrar que Archemar está equivocado, y este no es el caso.
Gilles 'SO- deja de ser malvado'
55
bueno, al menos ahora sé sobre el depurador de awk ...
Archemar