¿Hay alguna manera de eliminar líneas duplicadas en un archivo en Unix?
Puedo hacerlo con sort -u
y uniq
comandos, pero quiero usar sed
o awk
. ¿Es eso posible?
awk
, pero consumirá muchos recursos en archivos más grandes.
¿Hay alguna manera de eliminar líneas duplicadas en un archivo en Unix?
Puedo hacerlo con sort -u
y uniq
comandos, pero quiero usar sed
o awk
. ¿Es eso posible?
awk
, pero consumirá muchos recursos en archivos más grandes.
Respuestas:
awk '!seen[$0]++' file.txt
seen
es una matriz asociativa a la que Awk pasará cada línea del archivo. Si una línea no está en la matriz, seen[$0]
se evaluará como falsa. El !
es un operador lógico NOT e invertirá lo falso a verdadero. Awk imprimirá las líneas donde la expresión se evalúa como verdadera. Los ++
incrementos seen
para que seen[$0] == 1
después de la primera vez que se encuentre una línea y luego seen[$0] == 2
, y así sucesivamente.
Awk evalúa todo menos 0
y ""
(cadena vacía) a verdadero. Si se coloca una línea duplicada, seen
entonces !seen[$0]
se evaluará como falsa y la línea no se escribirá en la salida.
awk '!seen[$0]++' merge_all.txt > output.txt
for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
De http://sed.sourceforge.net/sed1line.txt : (Por favor, no me pregunten cómo funciona esto ;-))
# delete duplicate, consecutive lines from a file (emulates "uniq").
# First line in a set of duplicate lines is kept, rest are deleted.
sed '$!N; /^\(.*\)\n\1$/!P; D'
# delete duplicate, nonconsecutive lines from a file. Beware not to
# overflow the buffer size of the hold space, or else use GNU sed.
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
$!
necesaria la parte? ¿No sed 'N; /^\(.*\)\n\1$/!P; D'
hace lo mismo? No puedo encontrar un ejemplo en el que los dos sean diferentes en mi máquina (luego probé una línea vacía al final con ambas versiones y ambas estaban bien).
[ -~]
representa un rango de caracteres ASCII de 0x20 (espacio) a 0x7E (tilde). Estos se consideran los caracteres ASCII imprimibles (la página vinculada también tiene 0x7F / eliminar, pero eso no parece correcto). Eso hace que la solución se rompa para cualquiera que no use ASCII o cualquiera que use, digamos, caracteres de tabulación ... El más portátil [^\n]
incluye muchos más caracteres ... todos, excepto uno, de hecho.
Perl one-liner similar a la solución awk de @ jonas:
perl -ne 'print if ! $x{$_}++' file
Esta variación elimina los espacios en blanco finales antes de comparar:
perl -lne 's/\s*$//; print if ! $x{$_}++' file
Esta variación edita el archivo en el lugar:
perl -i -ne 'print if ! $x{$_}++' file
Esta variación edita el archivo en el lugar y realiza una copia de seguridad file.bak
perl -i.bak -ne 'print if ! $x{$_}++' file
La línea que Andre Miller publicó anteriormente funciona excepto para las versiones recientes de sed cuando el archivo de entrada termina con una línea en blanco y sin caracteres. En mi Mac, mi CPU simplemente gira.
Bucle infinito si la última línea está en blanco y no tiene caracteres :
sed '$!N; /^\(.*\)\n\1$/!P; D'
No se cuelga, pero pierdes la última línea
sed '$d;N; /^\(.*\)\n\1$/!P; D'
La explicación se encuentra al final de las preguntas frecuentes de sed :
El mantenedor de sed de GNU consideró que, a pesar de los problemas de portabilidad que
esto causaría, cambiar el comando N para imprimir (en lugar de
eliminar) el espacio del patrón era más coherente con las intuiciones de uno
sobre cómo debería comportarse un comando para "agregar la siguiente línea" .
Otro hecho que favoreció el cambio fue que "{N; command;}"
eliminará la última línea si el archivo tiene un número impar de líneas, pero
imprimirá la última línea si el archivo tiene un número par de líneas.Para convertir los scripts que usaban el comportamiento anterior de N (eliminar
el espacio del patrón al llegar al EOF) a scripts compatibles con
todas las versiones de sed, cambie una "N" solitaria. a "$ d; N;" .
$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5
La idea central es:
print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.
Explica:
$!N;
: si la línea actual NO es la última línea, use el N
comando para leer la siguiente línea pattern space
./^(.*)\n\1$/!P
: si el contenido de la corriente pattern space
está duplicate string
separado por dos \n
, lo que significa que la siguiente línea es same
con la línea actual, NO podemos imprimirlo de acuerdo con nuestra idea central; de lo contrario, lo que significa que la línea actual es la ÚLTIMA aparición de todas sus líneas consecutivas duplicadas, ahora podemos usar el P
comando para imprimir los caracteres en la pattern space
utilidad actual \n
( \n
también impresa).D
: utilizamos el D
comando para eliminar los caracteres en la pattern space
utilidad actual \n
( \n
también eliminada), luego el contenido depattern space
es la siguiente línea.D
comando obligará sed
a saltar a su FIRST
comando $!N
, pero NO leerá la siguiente línea del archivo o flujo de entrada estándar.$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5
La idea central es:
print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.
Explica:
:loop
comando set a label
named loop
.N
para leer la siguiente línea en el pattern space
.s/^(.*)\n\1$/\1/
para eliminar la línea actual si la siguiente línea es la misma que la línea actual, usamos el s
comando para realizar la delete
acción.s
comando se ejecuta con éxito, utilice la tloop
fuerza del comando sed
para saltar al label
nombre loop
, que hará el mismo bucle a las siguientes líneas, no hay líneas consecutivas duplicadas de la línea que es latest printed
; de lo contrario, use el D
comando para delete
la línea que es la misma que la latest-printed line
, y fuerce sed
para saltar al primer comando, que es el p
comando, el contenido de current pattern space
es la siguiente línea nueva.busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
Elimina las líneas duplicadas usando awk.
cat
es inútil. De todos modos, uniq
ya lo hace por sí mismo y no requiere que la entrada sea exactamente una palabra por línea.
uniq
solo es suficiente.