Elimine las líneas duplicadas manteniendo el orden de las líneas.

14
[root@server]# awk '!seen[$0]++' out.txt > cleaned
awk: (FILENAME=out.txt FNR=8547098) fatal error: internal error
Aborted
[root@server]#

El "" servidor "" tiene: 8 GByte RAM + 16 GByte SWAP, x> 300 GByte de espacio libre, amd64, CPU de escritorio. Linux científico 6.6. No se ejecuta nada más para hacer CARGA. Awk aborta después de unos segundos. Out.txt es ~ 1.6 GByte. GNU Awk 3.1.7.

Pregunta : ¿Cómo puedo eliminar las líneas duplicadas manteniendo el orden de las líneas? El caso también es importante, por ejemplo: "A" y "a" son dos líneas diferentes, hay que mantenerlo. Pero "a" y "a" están duplicados, solo se necesita el primero.

La respuesta podría estar en cualquier cosa ... si awk no es bueno para esto ... entonces perl / sed ... ¿cuál podría ser el problema?

[root@server]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61945
max locked memory       (kbytes, -l) 99999999
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 99999999
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61945
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
[root@server]# 

Actualización: Probé esto en una máquina RHEL, no aborta, pero no tuve tiempo de esperar a que termine ... ¿por qué SL Linux no difiere de RHEL?

Actualización: Estoy probando una conjetura virtual de Ubuntu 14 ... ¡hasta ahora funciona! No es un problema ulimit : mawk 1.3.3

root@asdf-VirtualBox:~# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 51331
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 51331
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
root@asdf-VirtualBox:~# 
somelooser28533
fuente
2
¿No hay líneas duplicadas en su ejemplo ...?
mikeserv
1
¿Qué son las awkversiones en dos máquinas?
Cuonglm
rhel actualizado y sl linux actualizado, no conozco la versión rhel .. sl es: GNU Awk 3.1.7
somelooser28533
¿Qué tan grande es out.txt? ¿Funciona el mismo comando si lo prueba en un archivo más pequeño? ¿Cuántos usuarios hay en la máquina? ¿Había suficiente memoria disponible para el proceso? ¿Hay algo especial en la línea 8547098 del archivo de entrada?
terdon

Respuestas:

22

Dudo que haga una diferencia, pero, por si acaso, aquí está cómo hacer lo mismo en Perl:

perl -ne 'print if ++$k{$_}==1' out.txt

Si el problema es mantener las líneas únicas en la memoria, tendrá el mismo problema awkque intentó. Entonces, otro enfoque podría ser:

cat -n out.txt | sort -k2 -k1n  | uniq -f1 | sort -nk1,1 | cut -f2-

Cómo funciona:

  1. En un sistema GNU, cat -nantepondrá el número de línea a cada línea siguiendo una cantidad de espacios y seguido de un carácter <tab> . catcanaliza esta representación de entrada a sort.

  2. sortLa -k2opción le indica que solo considere los caracteres desde el segundo campo hasta el final de la línea al ordenar, y sortdivide los campos de forma predeterminada en espacios en blanco (o catespacios insertados y <tab> ) .
    Cuando lo sigue -k1n, sortprimero considera el segundo campo y luego, en el caso de -k2campos idénticos , considera el primer campo pero ordenado numéricamente. Por lo tanto, las líneas repetidas se ordenarán juntas pero en el orden en que aparecieron.

  3. Los resultados se canalizan a uniq—que se le dice que ignore el primer campo ( -f1- y también como separados por espacios en blanco) — y que da como resultado una lista de líneas únicas en el archivo original y se canaliza de nuevo a sort.
  4. Esta vez se sortordena numéricamente en el primer campo ( catnúmero de línea insertado) , volviendo el orden de clasificación al que estaba en el archivo original y canalizando estos resultados cut.
  5. Por último, cutelimina los números de línea que fueron insertados por cat. Esto se efectúa cutimprimiendo solo desde el segundo campo hasta el final de la línea (y cutel delimitador predeterminado es un carácter <tab> ) .

Para ilustrar:

$ cat file
bb
aa
bb
dd
cc
dd
aa
bb
cc
$ cat -n file | sort -k2 | uniq -f1 | sort -k1 | cut -f2-
bb
aa    
dd
cc
terdon
fuente
Hola Terdon, el OP necesita mantener el orden de las líneas, por lo que el método cat | sort | uniq no funcionará ... Sin embargo, al igual que su versión perl ...
Lambert
1
Buena solución con sort! Pero la mayoría sortpuede hacerlo uniqpor sí mismo para que pueda acortar su guión sort -uk2 | sort -bk1,1n
Costas
@Costas es lo más sort? Pensé que -uera una característica de GNU.
terdon
@don_crissti ah, así es, gracias. ¿Pero cómo podría usarlo aquí? Como acabo de notar (y edité para corregir), primero necesito ordenar en el segundo campo y luego en el primero numéricamente para mantener el orden de las líneas. ¿Cómo puedo usar -uy especificar que debería ignorar el primer campo? Según man sort, -uesta no es una de las opciones posibles -f, por lo que no creo que se pueda usar aquí.
terdon
1
¡Esta es la transformación de Schwartz ! (+1)
JJoao
7
#!/usr/bin/perl 
use DB_File;
tie %h, 'DB_File';

while(<>){ not $h{$_} and print and $h{$_}=1 }

EDITAR 1: ¿Realmente funciona? (comparando)

Sol1 : Terdon et all Schwartzian-transform-like one-liner
    cat -n _1 | sort -uk2 | sort -nk1 | cut -f2-

Sol2 : perl  + DB_File (this answer)
    perl dbfile-uniq _1

Sol3 : PO (John W. Gill solution has a similar behavior)
    awk '!seen[$0]++' _1

Sol4: Terdon perl
    perl -ne 'print if ++$k{$_}==1' _1

Caso1 : 100_000_000 números aleatorios (5 dígitos cada uno), 566Mbytes, 31_212 valores diferentes:

$ while true ; do echo $RANDOM; done | head -100000000 > _1

Caso 2 : 50_000_000 números de rand (10 dígitos cada uno), 516Mbytes, 48_351_464 valores diferentes:

$ shuf _1 |  sed 'N;s/\n/ /' > _11

(los siguientes números no son muy precisos):

┌────────┬────────┬────────────────┬────────┬──────┐
         Sol1    Sol2            Sol3    Sol4 
         sort...│ perl DB         awk     perl 
├────────┼────────┼────────────────┼────────┼──────┤
 case 1  6m15    6m17            0m28    0m28 
├────────┼────────┼────────────────┼────────┴──────┤
 case 2  11m15   81m44           out of memory 
├────────┼────────┼────────────────┼────────┬──────┤
 case 2          5m54 /cache=2G               
└────────┴────────┴────────────────┴────────┴──────┘

sol2 con caché es:

use DB_File;
use Fcntl ;

$DB_HASH->{'cachesize'} = 2000_000_000;
tie %h, 'DB_File', "_my.db", O_RDWR|O_CREAT|O_TRUNC, 0640, $DB_HASH;

while(<>){ not $h{$_} and print and $h{$_}=1 }

Ordenar también se puede optimizar agregando una opción de tamaño de caché (no hecho).

Una conclusión rápida:

  • sort Es un comando fantástico!
JJoao
fuente
1
sort -uk2y sort -nk1,1son diferentes El primero considera desde la clave 2cd hasta el final de la línea, el segundo considera solo la primera clave. Debería cambiar su lugar sort -nk1allí; incluso podría ser más rápido de esa manera, pero definitivamente será más confiable. Por cierto, esas son algunas cajas bonitas.
mikeserv
@mikeserv, gracias por el comentario. Como K1,1 es único, sort -nk1 y sort -nk1,1 devuelven algún resultado. Probé ambos, el resultado fue el mismo y el tiempo no fue distintivo.
JJoao
Eso tiene sentido, aunque gracias por intentarlo. Entonces, cat -n¿una pestaña ? No sé cómo funciona ese comando.
mikeserv
1
@mikeserv, cat -ntransfrom felizmente cada uno lineen spaces + the number + \t + line- el formato ideal para ordenar y cortar
JJoao
1

he usado

awk -v BINMODE=rw '!($0 in a){a[$0];print}' infile >> outfile

BINMODE = rw: para mantener felices los terminadores de fin de línea. (Vivo en un entorno mixto)

La lógica es simple.

Si la línea actual no está en la matriz asociativa, agréguela a la matriz asociativa e imprima en la salida.

Puede haber limitaciones de memoria con este enfoque. Para archivos y conjuntos de archivos muy grandes, he usado variaciones sobre esto, usando el almacenamiento de archivos para superar las limitaciones.

John
fuente
0

La semántica de preservación del orden de su problema tiene una propiedad maravillosa: puede subdividir el problema. Puedes hacerlo split -l 1000000en el archivo de entrada; las piezas de 1000000 líneas que produce tienen nombres ordenados léxicamente, lo cual es bueno; luego unifique las piezas; y luego (como una segunda pasada) uniqify las salidas de esos.

Esto resuelve el problema de falta de memoria (limitando el requisito de memoria) a expensas de convertirlo en una solución multipass.

Específicamente:

Generar datos de entrada:

$ cat make-uniqm-input.py
#!/usr/bin/env python
import random
n = 1000000
for i in xrange(0, n):
    print random.randint(1000, 2000)

$ python make-uniqm-input.py  > uniqm-input.txt

$ wc -l uniqm-input.txt
 1000000 uniqm-input.txt

Dividir los datos de entrada:

$ split -l 10000 uniqm-input.txt

$ ls x?? | head
xaa
xab
xac
xad
xae
xaf
xag
xah
xai
xaj

$ ls x?? | wc -l
     100

$ cat x?? | wc -l
 1000000

Ejecute el uniqifier de una vez (conserva todas las líneas de entrada únicas en la memoria):

# 'uniqm' is any order-preserving uniq implementation, such as
# gawk '!counts[$0]++'.
$ uniqm < uniqm-input.txt > output-no-splitting.txt

$ wc -l output-no-splitting.txt
    1001 output-no-splitting.txt

Ejecute el unificador en piezas divididas (retiene solo líneas de entrada únicas de cada pieza en la memoria), luego reduzca como una segunda pasada:

$ for x in x??; do uniqm < $x; done | uniqm > output-with-splitting.txt

$ wc -l output-with-splitting.txt
    1001 output-with-splitting.txt

Comparar:

$ diff output-no-splitting.txt output-with-splitting.txt

$ head uniqm-input.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

$ head output-with-splitting.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

No sé la proporción de líneas únicas a no únicas en su entrada, ni qué tan bien mezcladas están las líneas de entrada, por lo que hay algunos ajustes que hacer en términos de la cantidad de archivos divididos que necesita.

John Kerl
fuente
0

Otro enfoque (que vale la pena publicar como respuesta separada) es: en lugar del enfoque de archivo dividido que crea archivos temporales, realice el procesamiento por lotes dentro del propio software uniqifier. Por ejemplo, usando una implementación de uniqifier Ruby con fines explicativos:

require 'set'
line_batch_count = 50000 # tunable parameter
lines_seen = Set.new
line_number = 0
ARGF.each do |line|
   line_number += 1
   if (line_number % line_batch_count) == 0
     lines_seen.clear
   end
   unless lines_seen.include? line
      puts line
      lines_seen << line
   end
end

La idea es limpiar el hash-set de vez en cuando. Entonces esto se vuelve iterativo:

$ cat uniqm-input.txt | ruby uniqm-capped.rb | wc -l
   20021

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | wc -l
    1001

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | head
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

Por lo tanto, puede ejecutar esta versión limitada repetidamente, hasta que el recuento de líneas no cambie de una iteración a la siguiente.

Tenga en cuenta que esta técnica capi-uniqm es independiente del lenguaje: puede borrar la lines_seenmatriz cada N líneas si está utilizando awk, python, perl, C ++, etc. Existen métodos de limpieza clara para todos estos idiomas; Creo que awk's deleteno es estándar, pero común.

John Kerl
fuente