2009-09-20 12 views

Respuesta

1

grep puede hacerlo, pero supongo que tendrá un tiempo mucho más fácil con awk (aka gawk, en algunos sistemas).

La cadena/script efectiva que se utilizará para su necesidad depende de unos pocos bits adicionales de información. Por ejemplo, el archivo de entrada es fácilmente ordenados, lo grande que es la entrada (o más bien es enorme o una corriente) ...

Suponiendo entrada ordenada (ya sea inicialmente o de la tubería a través de una especie), el script awk sería algo así: (atención no probada)

Consulte la solución provista por Jonathan Leffler o Hai Vu, para obtener el mismo sin requisito de ordenación previa.

#!/usr/bin/awk 
# *** Simple AWK script to output duplicate lines found in input *** 
# Assume input is sorted on fields 

BEGIN { 
    FS = ";"; #delimiter 
    dupCtr = 0;  # number of duplicate _instances_ 
    dupLinesCtr = 0; # total number of duplicate lines 

    firstInSeries = 1; #used to detect if this is first in series 

    prevLine = ""; 
    prevCol2 = ""; # use another string in case empty field is valid 
} 

{ 
    if ($2 == prevCol2) { 
    if (firstInSeries == 1) { 
     firstInSeries = 0; 
     dupCtr++; 
     dupLinesCtr++; 
     print prevLine 
    } 
    dupLinesCtr++; 
    print $0 
    } 
    else 
    firstInSeries = 1 
    prevCol2 = $2 
    prevLine = $0 
} 

END { #optional display of counts etc. 
    print "*********" 
    print "Total duplicate instances = " iHits " Total lines = " NR; 
} 
+0

No tengo bieleve Tengo esa herramienta. Cuando escribo "awk --help" no recibo tal mensaje de opción – goe

+0

Hum ... tal vez tienes boquiabierto, que de hecho es "mejor" (en mi humilde opinión) – mjv

+0

gawk tampoco existe – goe

6

Tienen una intrincada secuencia awk.

awk 'BEGIN { FS=";" } { c[$2]++; l[$2,c[$2]]=$0 } END { for (i in c) { if (c[i] > 1) for (j = 1; j <= c[i]; j++) print l[i,j] } }' file.txt 

Funciona manteniendo un contador de todas las ocurrencias de cada valor en el segundo campo, y las líneas que tienen ese valor, entonces se imprime las líneas que tienen contadores mayor que 1.

Reemplazar todas las instancias de $2 con el número de campo que necesite, y el file.txt al final con su nombre de archivo.

+0

¡Convolucionado y funciona! No se puede superar eso :-) –

+1

Puede, realmente: complicado y funciona * y * ¡está documentado! – jtbandes

3

Como @mjv conjeturó - awk (o Perl, Python o) es una mejor opción:

awk -F';' ' { 
    if (assoc[$2]) {   # This field 2 has been seen before 
     if (assoc[$2] != 1) { # The first occurrence has not been printed 
      print assoc[$2]; # Print first line with given $2 
      assoc[$2] = 1; # Reset array entry so we know we've printed it; 
           # a full line has 8 fields with semi-colons and 
           # cannot be confused with 1. 
     } 
     print $0;    # Print this duplicate entry 
    } 
    else { 
     assoc[$2] = $0;  # Record line in associative array, indexed by 
           # second field. 
    } 
}' <<! 
a;b;c;d;e;f;g;h 
a;c;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;2;c;d;e;f;g;h 
a;z;c;d;e;f;g;h 
a;q;c;d;e;f;g;h 
a;4;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;x;c;d;e;f;g;h 
a;c;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;q;c;d;e;f;g;h 
a;4;c;d;e;f;g;h 
! 

Esto funciona, pero puede volver a ordenar un poco los datos - ya que imprime la primera aparición de una línea duplicada cuando aparece la segunda aparición La salida de la muestra es:

a;1;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;c;c;d;e;f;g;h 
a;c;c;d;e;f;g;h 
a;1;c;d;e;f;g;h 
a;q;c;d;e;f;g;h 
a;q;c;d;e;f;g;h 
a;4;c;d;e;f;g;h 
a;4;c;d;e;f;g;h 

Esta variante de la secuencia de comandos awk vuelve a organizar la prueba, lo que lleva a una notación ligeramente más compacto. También ignora explícitamente líneas de datos mal formadas que no contienen 8 campos separados por punto y coma. Está empaquetado como un script de shell, pero sin ningún tipo de manejo de opciones, por lo que solo puede proporcionar una lista de archivos para escanear (lee la entrada estándar si no hay archivos en la lista). Eliminé los puntos y coma Perl-ish en el script; awk no los necesita.

#!/bin/sh 

awk -F';' ' 
NF == 8 { 
    if (!assoc[$2]) assoc[$2] = $0 
    else if (assoc[$2] != 1) 
    { 
     print assoc[$2] 
     assoc[$2] = 1 
     print $0 
    } 
    else print $0 
}' "[email protected]" 

Además, @mjv comentó que podría haber problemas de memoria con una solución como ésta si la entrada es enorme, ya que mantiene un registro de cada valor distinto de campo 2 en la matriz asociativa 'Assoc'. Podemos eliminar eso si los datos alimentados en awk están ordenados, algo que podemos asegurar usando sort, por supuesto.Aquí hay un script variante que no lidiar con entradas monstruosas (porque sort derrames de datos en el disco si es necesario para mantener los resultados intermedios):

sort -t';' -k 2,2 "[email protected]" | 
awk -F';' ' 
BEGIN { last = ";"; line = "" } 
NF == 8 { 
    if ($2 != last) 
    { 
     last = $2 
     line = $0 
    } 
    else if (line != "") 
    { 
     print line 
     line = "" 
     print $0 
    } 
    else print $0; 
}' 

Esto sólo se conserva una copia de una línea de entrada. El resultado de los datos de muestra se da en orden ordenado, por supuesto.

+0

En su último ejemplo, la última línea nunca se imprime. Eso es lo que hay que hacer si es un engaño de la penúltima línea. Pero si es una línea única, debería imprimirse. Necesitas algún tipo de '{END if (line! =" ") Línea de impresión}' pero mi AWK es una mierda. –

+0

@LeeMeador: Como el objetivo que se da en la pregunta es solo para imprimir líneas que son duplicadas (bajo la restricción de que es una entrada duplicada en la columna 2), el código anterior es correcto para omitir la última línea si es única, isn no es así? –

16

Ver mis comentarios en el script awk

$ cat data.txt 
John Thomas;jd;301 
Julie Andrews;jand;109 
Alex Tremble;atrem;415 
John Tomas;jd;302 
Alex Trebe;atrem;416 

$ cat dup.awk 
BEGIN { FS = ";" } 

{ 
    # Keep count of the fields in second column 
    count[$2]++; 

    # Save the line the first time we encounter a unique field 
    if (count[$2] == 1) 
     first[$2] = $0; 

    # If we encounter the field for the second time, print the 
    # previously saved line 
    if (count[$2] == 2) 
     print first[$2]; 

    # From the second time onward. always print because the field is 
    # duplicated 
    if (count[$2] > 1) 
     print 
} 

Ejemplo de salida:

$ sort -t ';' -k 2 data.txt | awk -f dup.awk 

John Thomas;jd;301 
John Tomas;jd;302 
Alex Tremble;atrem;415 
Alex Trebe;atrem;416 

Aquí está mi solución # 2:

awk -F';' '{print $2}' data.txt |sort|uniq -d|grep -F -f - data.txt 

La belleza de esta solución es preservar el orden de línea en el e xpense de usar muchas herramientas juntas (awk, sort, uniq y fgrep).

El comando awk imprime el segundo campo, cuya salida se ordena. A continuación, el comando uniq -d selecciona las cadenas duplicadas. En este punto, el resultado estándar contiene una lista de segundos campos duplicados, uno por línea. Luego colocamos esa lista en fgrep. La bandera '-f -' le dice a fgrep que busque estas cadenas desde la entrada estándar.

Sí, puede salir todo con la línea de comando. Me gusta más la segunda solución para ejercitar muchas herramientas y para una lógica más clara (al menos para mí). El inconveniente es la cantidad de herramientas y posiblemente la memoria utilizada. Además, la segunda solución es ineficiente porque escanea el archivo de datos dos veces: la primera vez con el comando awk y la segunda con el comando fgrep. Esta consideración solo importa cuando el archivo de entrada es grande.

+0

El uso de matrices separadas para el recuento y la primera instancia de línea es quizás más ordenado que mi versión con una matriz para ambas tareas. –

+0

Gran solución. ¡Evita el requisito de ordenación previa! – mjv

+0

Tenga cuidado con la clasificación no estable. Puede informar que la primera línea es un duplicado de la segunda. –

0

¿qué tal:

sort -t ';' -k 2 test.txt | awk -F';' 'BEGIN{curr="";prev="";flag=0} \ 
        NF==8{ prev=curr; 
          curr=$2; 
          if(prev!=curr){flag=1} 
          if(flag!=0 && prev==curr)flag++ ; 
          if(flag==2)print $0}' 

también probé uniq orden que tiene la opción para mostrar líneas repetidas "-d" pero incapaz de averiguar si se puede utilizar con los campos.

+0

Tenga cuidado con la clasificación no estable. Puede informar que la primera línea es un duplicado de la segunda. –

1

préstamos de Hai Vu:

% cat data.txt 
John Thomas;jd;301 
Julie Andrews;jand;109 
Alex Tremble;atrem;415 
John Tomas;jd;302 
Alex Trebe;atrem;416 

Ahí está la manera realmente fácil (con GNU-tipo & gawk): (salida A pesar de esto será re-orden)

(Advertencia : Sin --establecido, el género puede reordenar líneas para que la segunda ocurrencia sea anterior a la primera. ¡Cuidado con eso!)

cat data.txt | sort -k2,2 -t';' --stable | gawk -F';' '{if ($2==old) { print $0 }; old=$2; }' 

Hay también la forma en que Perl ...

cat data.txt | perl -e 'while(<>) { @data = split(/;/); if (defined($test{$data[1]})) { print $_; } $test{$data[1]} = $_; }' 

.

0

Supongo que no depende de ningún pedido en particular de la entrada (que puede que no haya sido ordenado previamente en el campo de la tecla (segundo)) y que prefiera conservar el orden de las líneas de entrada en su salida ... imprimiendo copias de la primera y todas las líneas subsiguientes que contienen valores duplicados en el segundo campo.

He aquí el fragmento de código más rápido que pude llegar a en Python:

import fileinput 
    seen = dict() 
    for line in fileinput.input(): 
     fields = line.split(';') 
     key = fields[1] 
     if key in seen: 
      if not seen[key][0]: 
       print seen[key][1], 
       seen[key] = (True, seen[key][1]) 
      print line, 
     else: 
      seen[key] = (False, line) 

El módulo fileinput nos permite manejar nuestras líneas de entrada de una manera similar al procesamiento por defecto awk archivo/o de entrada ... a la semántica del interruptor de línea de comando de Perl -n.

A partir de ahí, simplemente hacemos un seguimiento de la primera línea que vemos con un valor único en el segundo campo, y una bandera que indica si hemos impreso esto antes. Cuando encontramos por primera vez un duplicado, imprimimos la primera línea que tenía esa clave y la marcamos como impresa, luego imprimimos la línea actual. Para todos los duplicados posteriores, simplemente imprimimos la línea actual. Obviamente, para cualquier persona que no esté engañada simplemente lo publicamos como una entrada a nuestro diccionario.

Probablemente haya una forma más elegante de manejar ese booleano de "primer engaño" ... pero esto fue lo más obvio para mí y no debería suponer ninguna acción adicional adicional. Crear una clase/objeto muy simple con su propio estado (me han impreso) sería una opción. Pero creo que eso haría que la esencia general del código sea más difícil de entender.

Debería ser obvio que esto se puede hacer en cualquier script o lenguaje de programación que soporte para arrays asociativos (hash, diccionarios, tablas, cualquiera que sea el idioma que prefiera). La única diferencia entre este código y la mayoría de los otros ejemplos que he visto en este hilo está en las suposiciones que estoy haciendo sobre sus requisitos (que preferiría preservar el orden relativo de las líneas de entrada y salida).

0

Simple awk único método para eliminar filas únicas según la columna n.º 2 (o devolver filas duplicadas según la columna n.º 2); Es posible que deba cambiar a la columna de destino esperada o a la combinación de varias columnas $X$Y.

awk -F\; 'NR==FNR{s[$2]++;next} (s[$2]>1)' infile infile 
Cuestiones relacionadas