![¿Cómo funciona awk '!a[$0]++'?](https://rvso.com/image/57077/%C2%BFC%C3%B3mo%20funciona%20awk%20'!a%5B%240%5D%2B%2B'%3F.png)
Este resumen elimina líneas duplicadas de la entrada de texto sin realizar una clasificación previa.
Por ejemplo:
$ cat >f
q
w
e
w
r
$ awk '!a[$0]++' <f
q
w
e
r
$
El código original que encontré en Internet decía:
awk '!_[$0]++'
Esto fue aún más desconcertante para mí ya que supuse _
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 la frase: cada línea de entrada se utiliza como clave en una matriz hash, por lo que, al finalizar, el hash contiene líneas únicas en el orden de llegada.
Lo que me gustaría saber es cómo awk interpreta exactamente esta notación. Por ejemplo, qué significa el signo de explosión ( !
) y los demás elementos de este fragmento de código.
¿Como funciona?
Respuesta1
Aquí hay una respuesta "intuitiva". Para obtener una explicación más detallada del mecanismo de awk, consulte @Cuonglm
En este caso, !a[$0]++
el post-incremento ++
se puede dejar de lado por un momento, no cambia el valor de la expresión. Entonces, mira solo !a[$0]
. Aquí:
a[$0]
usa la línea actual $0
como clave de la matriz a
, tomando el valor almacenado allí. Si nunca antes se hizo referencia a esta clave en particular, a[$0]
se evalúa como una cadena vacía.
!a[$0]
El !
niega el valor de antes. Si estaba vacío o era cero (falso), ahora tenemos un resultado verdadero. Si fue distinto de cero (verdadero), tenemos un resultado falso. Si toda la expresión se evaluó como verdadera, lo que significa que a[$0]
no estaba configurada desde el principio, la línea completa se imprime como acción predeterminada.
Además, independientemente del valor anterior, el operador post-incremento agrega uno a a[$0]
, por lo que la próxima vez que se acceda al mismo valor en la matriz, será positivo y toda la condición fallará.
Respuesta2
Aquí está el procesamiento:
a[$0]
: mira el valor de la clave$0
, en una matriz asociativaa
. Si no existe, créelo automáticamente con una cadena vacía.a[$0]++
: incrementa el valor dea[$0]
, devuelve el valor anterior como valor de expresión. El++
operador devuelve un valor numérico, por lo que sia[$0]
estaba vacío al principio,0
se devuelve ya[$0]
se incrementa a1
.!a[$0]++
: niega el valor de la expresión. Sia[$0]++
se devuelve0
(un valor falso), toda la expresión se evalúa como verdadera y hace queawk
se realice la acción predeterminadaprint $0
. De lo contrario, si toda la expresión se evalúa como falsa, no se realizan más acciones.
Referencias:
Con gawk
, podemos usardgawk (o awk --debug
con una versión más reciente)para depurar un gawk
script. Primero, cree un gawk
script, 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 que Op_postincrement
fue ejecutado antes Op_not
.
También puedes usar si
o stepi
en lugar de s
o step
para 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++;
Respuesta3
Ah, el omnipresente pero también siniestro eliminador de duplicados awk.
awk '!a[$0]++'
Este dulce bebé es el hijo amado del poder y la concisión de Awk. el pináculo de awk one liners. Corto pero poderoso y arcano a la vez. elimina duplicados manteniendo el orden. una hazaña no lograda por uniq
o sort -u
que elimina solo duplicados adyacentes o tiene que romper el orden para eliminar duplicados.
Aquí está mi intento de explicar cómo funciona esta frase extraña. Me esforcé en explicar las cosas para que alguien que no sepa nada de awk pueda seguirlo. Espero haber podido hacerlo.
Primero, algunos antecedentes: awk es un lenguaje de programación. este comando awk '!a[$0]++'
invoca el intérprete/compilador de awk en el código de awk !a[$0]++
. similar a python -c 'print("foo")'
o node -e 'console.log("foo")'
. El código de awk suele ser de una sola línea porque awk fue diseñado específicamente para ser conciso para el filtrado de texto.
ahora un pseudocódigo. Lo que hace este delineador es básicamente lo siguiente:
for every line of input
if i have not seen this line before then
print line
take note that i have now seen this line
Espero que puedas ver cómo esto elimina duplicados manteniendo el orden.
pero ¿cómo caben un bucle, un if, una impresión y un mecanismo para almacenar y recuperar cadenas en 8 caracteres de código awk? la respuesta es implícita.
el bucle, el if y la impresión están implícitos.
Para explicarlo, examinemos nuevamente algún pseudocódigo:
for every line of input
if line matches condition then
execute code block
Este es un filtro típico que probablemente haya escrito mucho de una forma u otra en su código en cualquier idioma. El lenguaje awk está diseñado para que escribir este tipo de filtros sea muy breve.
awk hace el ciclo por nosotros, por lo que solo necesitamos escribir el código dentro del ciclo. la sintaxis de awk omite aún más el texto estándar de un if y solo necesitamos escribir la condición y el bloque de código:
condition { code block }
En awk esto se llama "regla".
podemos omitir la condición o el bloque de código (obviamente no podemos omitir ambos) y awk completará la parte que falta con algunos implícitos.
si omitimos la condición
{ code block }
entonces será implícitamente verdadero
true { code block }
lo que significa que el bloque de código se ejecutará para cada línea
si omitimos el bloque de código
condition
entonces será implícita imprimir la línea actual
condition { print current line }
echemos un vistazo a nuestro código awk original nuevamente
!a[$0]++
no se encuentra entre llaves, por lo que es la parte condicional de una regla.
escribamos el bucle implícito y si e imprimamos
for every line of input
if !a[$0]++ then
print line
comparar con nuestro pseudocódigo original
for every line of input # implicit by awk
if i have not seen this line before then # at least we know the conditional part
print line # implicit by awk
take note that i have now seen this line # ???
entendemos el bucle, el if y la impresión. pero ¿cómo funciona para que se evalúe como falso solo en líneas duplicadas? ¿Y cómo toma nota de las líneas ya vistas?
desmantelemos esta bestia:
!a[$0]++
Si conoce algo de C o Java, ya debería conocer algunos de los símbolos. la semántica es idéntica o al menos similar.
el signo de exclamación ( !
) es un negativo. evalúa la expresión como booleana y cualquiera que sea el resultado, se niega. si la expresión se evalúa como verdadera, el resultado final es falso y viceversa.
a[..]
es una matriz. una matriz asociativa. otros idiomas lo llaman mapa o diccionario. en awk todas las matrices son matrices asociativas. el a
no tiene ningún significado especial. es sólo un nombre para la matriz. también podría ser x
o eliminatetheduplicate
.
$0
es la línea actual de la entrada. esta es una variable específica de awk.
el plus plus ( ++
) es un operador de incremento posterior. Este operador es un poco complicado porque hace dos cosas: se incrementa el valor de la variable. pero también "devuelve" el valor original, no incrementado, para su posterior procesamiento.
! a[ $0 ] ++
negator array current line post increment
¿Como trabajan juntos?
aproximadamente en este orden:
$0
es la linea actuala[$0]
es el valor en la matriz para la línea actual- el incremento posterior (
++
) obtiene el valor dea[$0]
; lo incrementa y lo almacena nuevamente ena[$0]
; luego "devuelve" el valor original al siguiente operador de la línea: el negativo. - el negador (
!
) obtiene un valor del++
cual era el valor originala[$0]
; se evalúa como booleano, luego se niega y luego se pasa al if implícito. - el if luego decide si imprimir la línea o no.
eso significa si la línea se imprime o no, o en el contexto de este programa awk: si la línea es un duplicado o no, lo decide en última instancia el valor en a[$0]
.
por extensión: el mecanismo que toma nota si esta línea ya se ha visto debe ocurrir cuando ++
se almacena el valor incrementado nuevamente en a[$0]
.
echemos un vistazo a nuestro pseudocódigo nuevamente
for every line of input
if i have not seen this line before then # decided based on value in a[$0]
print line
take note that i have now seen this line # happens by increment from ++
Es posible que algunos de ustedes ya hayan visto cómo se desarrolla esto, pero hemos llegado hasta aquí. Demos los últimos pasos y desarmemoremos los++
comenzamos con el código awk incrustado en los implícitos
for each line as $0
if !a[$0]++ then
print $0
introduzcamos variables para tener algo de espacio para trabajar
for each line as $0
tmp = a[$0]++
if !tmp then
print $0
ahora desarmamos ++
.
recuerde que este operador hace dos cosas: incrementar el valor en la variable y devolver el valor original para su posterior procesamiento. entonces el ++
se convierte en dos líneas:
for each line as $0
tmp = a[$0] # get original value
a[$0] = tmp + 1 # increment value in variable
if !tmp then
print $0
o en otras palabras
for each line as $0
tmp = a[$0] # query if have seen this line
a[$0] = tmp + 1 # take note that has seen this line
if !tmp then
print $0
comparar con nuestro primer pseudocódigo
for every line of input:
if i have not seen this line before:
print line
take note that i have now seen this line
Así que ahí lo tenemos. tenemos el bucle, el if, la impresión, la consulta y la toma de notas. solo que en un orden diferente al del pseudocódigo.
condensado a 8 caracteres
!a[$0]++
posible debido al bucle implícito de awks, si implícito, impresión implícita y porque ++
realiza tanto la consulta como la toma de notas.
Queda una pregunta. ¿Cuál es el valor de a[$0]
la primera línea? ¿O por alguna línea que no se haya visto antes? la respuesta vuelve a ser implícita.
en awk, cualquier variable que se use por primera vez se declara implícitamente y se inicializa en una cadena vacía. excepto matrices. Las matrices se declaran e inicializan en una matriz vacía.
Esto ++
implica conversiones implícitas a números. la cadena vacía se convierte a cero. otras cadenas se convertirán en un número mediante algún algoritmo de mejor esfuerzo. si la cadena no se reconoce como un número, se convierte nuevamente a cero.
La !
conversión implícita a booleana. el número cero y la cadena vacía se convierte en falso. cualquier otra cosa se convierte en verdadera.
eso significa que cuando se ve una línea por primera vez, a[$0]
se establece en la cadena vacía. la cadena vacía se convierte a cero ++
(también se incrementa a 1 y se almacena nuevamente en a[$0]
). el cero se convierte en falso por !
. el resultado !
es verdadero, por lo que la línea se imprime.
el valor en a[$0]
es ahora el número 1.
Si se ve una línea la segunda vez, entonces a[$0]
el número 1 se convierte en verdadero y el resultado !
es falso, por lo que no se imprime.
cualquier encuentro posterior de la misma línea aumenta el número. Dado que todos los números excepto cero son verdaderos, el resultado !
siempre será falso, por lo que la línea nunca se volverá a imprimir.
así es como se eliminan los duplicados.
TL;DR: cuenta la frecuencia con la que se ha visto una línea. si es cero, imprima. si hay otro número, entonces no se imprime. puede ser breve debido a muchas implicaciones.
Bonificación: algunas variantes del one liner y una explicación súper breve de lo que hace.
reemplazar $0
(línea completa) con $2
(segunda columna) eliminará los duplicados, pero solo en función de la segunda columna
$ cat input
x y z
p q r
a y b
$ awk '!a[$2]++' input
x y z
p q r
reemplace !
(negador) con ==1
(igual a uno) e imprimirá la primera línea que es un duplicado
$ cat input
a
b
c
c
b
b
$ awk 'a[$0]++==1' input
c
b
reemplazar con >0
(mayor que cero) y agregar {print NR":"$0}
imprimirá todas las líneas duplicadas con el número de línea. NR
es una variable awk especial que contiene el número de línea (número de registro en la jerga awk).
$ awk 'a[$0]++>0 {print NR":"$0}' input
4:c
5:b
6:b
Espero que estos ejemplos ayuden a comprender mejor los conceptos explicados anteriormente.
Respuesta4
Solo quiero agregar que ambos expr++
y ++expr
son solo una abreviatura de expr=expr+1
. Pero
$ awk '!a[$0]++' f # or
$ awk '!(a[$0]++)' f
imprimirá todos los valores únicos ya que expr++
se evaluará expr
antes de la suma, mientras que
$ awk '!(++a[$0])' f
simplemente no imprimirá nada ya que ++expr
se evaluará como expr+1
, que siempre devolverá un valor distinto de cero en este caso, y la negación siempre devolverá un valor cero.