¿Cómo puedo eliminar líneas duplicadas en un archivo en Unix?
shell scripting (7)
La primera solución también es de http://sed.sourceforge.net/sed1line.txt
$ 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, use el comandoN
para leer la siguiente línea en elpattern space
. -
/^(.*)/n/1$/!P
: si el contenido delpattern space
actual es dosduplicate string
separadas por/n
, lo que significa que la siguiente línea es lasame
la línea actual, NO podemos imprimir 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 comandoP
para imprimir los caracteres en el usopattern space
actual/n
(/n
también impreso). -
D
: usamos el comandoD
para eliminar los caracteres en el usopattern space
actual/n
(/n
también eliminado), luego el contenido delpattern space
delpattern space
es la siguiente línea. - y el comando
D
obligará ased
a saltar a suFIRST
comando$!N
, pero NO leerá la siguiente línea desde el archivo o flujo de entrada estándar.
La segunda solución es fácil de entender (de mí mismo):
$ 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:
- lea una nueva línea de flujo de entrada o archivo e imprímalo una vez.
- use
:loop
command establece unalabel
llamadaloop
. - usa
N
para leer la siguiente línea en elpattern space
. - use
s/^(.*)/n/1$//1/
para eliminar la línea actual si la siguiente línea es la misma que la línea actual, usamoss
comandos
para realizar la acción dedelete
. - si el comando
s
se ejecuta con éxito, use el comandotloop
forcesed
para saltar a lalabel
llamadaloop
, que hará el mismo loop a las siguientes líneas util no hay líneas consecutivas duplicadas de lalatest printed
; de lo contrario, use el comandoD
paradelete
la línea que es la misma con lalatest-printed line
, y fuerce ased
para saltar al primer comando, que es el comandop
, el contenido delpattern space
actual es la siguiente línea nueva.
¿Hay alguna manera de eliminar líneas duplicadas en un archivo en Unix?
Puedo hacerlo con comandos sort -u
y uniq
, pero quiero usar sed
o awk
. ¿Es eso posible?
De http://sed.sourceforge.net/sed1line.txt : (Por favor, no me preguntes 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''
El texto único que publicó Andre Miller funciona a excepción de 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 cuelga, pero pierdes la última línea
sed ''$d;N; /^/(.*/)/n/1$/!P; D''
La explicación está al final de las preguntas frecuentes sed :
El mantenedor de sed GNU sintió que a pesar de los problemas de portabilidad
esto causaría, cambiar el comando N para imprimir (en lugar de
eliminar) el espacio del patrón era más consistente con las propias intuiciones
acerca de cómo debe comportarse un comando para "agregar la siguiente línea".
Otro hecho que favoreció el cambio fue que "{N; command;}"
elimine la última línea si el archivo tiene un número impar de líneas, pero
imprime la última línea si el archivo tiene un número par de líneas.Para convertir scripts que utilizaron el comportamiento anterior de N (eliminación
el espacio del patrón al llegar al EOF) a scripts compatibles con
todas las versiones de sed, cambian una "N" solitaria a "$ d; N" .
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 in situ:
perl -i -ne ''print if ! $x{$_}++'' file
Esta variación edita el archivo in situ y file.bak
una copia de seguridad file.bak
perl -i.bak -ne ''print if ! $x{$_}++'' file
Una forma alternativa de usar Vim (Vi compatible) :
Eliminar líneas duplicadas y consecutivas de un archivo:
vim -esu NONE +''g//v^(.*)/n/1$/d'' +wq
Elimine líneas duplicadas, no consecutivas y no vacías de un archivo:
vim -esu NONE +''g//v^(.+)$/_.{-}^/1$/d'' +wq
awk ''!seen[$0]++'' file.txt
seen
es una matriz asociativa que Awk pasará a cada línea del archivo. Si una línea no está en la matriz, entonces seen[$0]
se evaluará a falso. El !
es un operador NOT lógico e invertirá el falso en verdadero. Awk imprimirá las líneas donde la expresión se evalúa como verdadera. Los incrementos de ++
seen
modo tal que se seen[$0] == 1
después de la primera vez que se encuentra una línea y luego se seen[$0] == 2
, y así sucesivamente.
Awk evalúa todo excepto 0
y ""
(cadena vacía) en verdadero. Si se coloca una línea duplicada en seen
entonces !seen[$0]
se evaluará como falso y la línea no se escribirá en la salida.
cat filename | sort | uniq -c | awk -F" " ''$1<2 {print $2}''
Elimina las líneas duplicadas usando awk.