Identificar líneas duplicadas en un archivo sin eliminarlas?


11

Tengo mis referencias como un archivo de texto con una larga lista de entradas y cada una tiene dos (o más) campos.

La primera columna es la url de la referencia; la segunda columna es el título que puede variar un poco dependiendo de cómo se realizó la entrada. Lo mismo para el tercer campo que puede o no estar presente.

Quiero identificar pero no eliminar entradas que tienen el primer campo (URL de referencia) idéntico. Lo sé, sort -k1,1 -upero eso eliminará automáticamente (de forma no interactiva) todos menos el primer golpe. ¿Hay alguna manera de avisarme para que pueda elegir cuál conservar?

En el extracto a continuación de tres líneas que tienen el mismo primer campo ( http://unix.stackexchange.com/questions/49569/), me gustaría mantener la línea 2 porque tiene etiquetas adicionales (ordenar, CLI) y eliminar las líneas # 1 y # 3:

http://unix.stackexchange.com/questions/49569/  unique-lines-based-on-the-first-field
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field   sort, CLI
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field

¿Existe un programa para ayudar a identificar tales "duplicados"? Entonces, ¿puedo limpiar manualmente eliminando personalmente las líneas 1 y 3?


No entiendo bien su ejemplo ... ¿podría dar una versión más simplificada de la entrada y la salida esperada?
Oli

¿Ver si ahora está más claro?
DK Bose

Respuestas:


9

Si entiendo tu pregunta, creo que necesitas algo como:

for dup in $(sort -k1,1 -u file.txt | cut -d' ' -f1); do grep -n -- "$dup" file.txt; done

o:

for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n -- "$dup" file.txt; done

¿Dónde file.txtestá interesado su archivo que contiene datos sobre usted?

En la salida verá el número de líneas y líneas donde se encuentra el primer campo dos o más veces.


3
Gracias: incluso cut -d " " -f1 file.txt | uniq -dme da buena salida.
DK Bose

@DKBose Probablemente hay más posibilidades, pero también quería usar tu comando.
Radu Rădeanu

Gracias. El segundo comando es el que me gusta. Puedes quitar el primero. Y si explicas el código, eso también sería bueno :)
DK Bose

10

Este es un problema clásico que se puede resolver con el uniqcomando. uniqpuede detectar líneas consecutivas duplicadas y eliminar duplicados ( -u, --unique) o mantener solo duplicados ( -d, --repeated).

Dado que el orden de las líneas duplicadas no es importante para usted, primero debe ordenarlo. Luego, use uniqpara imprimir solo líneas únicas:

sort yourfile.txt | uniq -u

También hay una opción -c( --count) que imprime el número de duplicados para la -dopción. Vea la página del manual de uniqpara más detalles.


Si realmente no le importan las partes después del primer campo, puede usar el siguiente comando para buscar claves duplicadas e imprimir cada número de línea (agregue otro | sort -npara ordenar la salida por línea):

 cut -d ' ' -f1 .bash_history | nl | sort -k2 | uniq -s8 -D

Como desea ver líneas duplicadas (usando el primer campo como clave), no puede usar directamente uniq. El problema que dificulta la automatización es que las partes del título varían, pero un programa no puede determinar automáticamente qué título debe considerarse el último.

Aquí hay un script AWK (guárdelo script.awk) que toma su archivo de texto como entrada e imprime todas las líneas duplicadas para que pueda decidir cuál eliminar. ( awk -f script.awk yourfile.txt)

#!/usr/bin/awk -f
{
    # Store the line ($0) grouped per URL ($1) with line number (NR) as key
    lines[$1][NR] = $0;
}
END {
    for (url in lines) {
        # find lines that have the URL occur multiple times
        if (length(lines[url]) > 1) {
            for (lineno in lines[url]) {
                # Print duplicate line for decision purposes
                print lines[url][lineno];
                # Alternative: print line number and line
                #print lineno, lines[url][lineno];
            }
        }
    }
}

Creo que esto está cerca de lo que quiero pero necesito lo contrario de `-f, --skip-fields = N (evite comparar los primeros N campos). En otras palabras, quiero que solo se considere el primer campo, las URL.
DK Bose

@DKBose Hay una opción -w( --check-chars) para limitar a un número fijo de caracteres, pero al ver su ejemplo, tiene primeros campos variables. Como uniqno admite la selección de campos, debe utilizar una solución alternativa. Incluiré un ejemplo de AWK ya que es más fácil.
Lekensteyn

Sí, solo estaba mirando -wpero la longitud del primer campo es variable :(
DK Bose

@DKBose Por favor, vea la última edición
Lekensteyn

1
Estoy obteniendo awk: script.awk: línea 4: error de sintaxis en o cerca de [awk: script.awk: línea 10: error de sintaxis en o cerca de [awk: script.awk: línea 18: error de sintaxis en o cerca}
DK Bose

2

Si leo esto correctamente, todo lo que necesitas es algo como

awk '{print $1}' file | sort | uniq -c | 
    while read num dupe; do [[ $num > 1 ]] && grep -n -- "$dupe" file; done

Eso imprimirá el número de la línea que contiene el engaño y la línea misma. Por ejemplo, usando este archivo:

foo bar baz
http://unix.stackexchange.com/questions/49569/  unique-lines-based-on-the-first-field
bar foo baz
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field   sort, CLI
baz foo bar
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field

Producirá esta salida:

2:http://unix.stackexchange.com/questions/49569/  unique-lines-based-on-the-first-field
4:http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field   sort, CLI
6:http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field

Para imprimir solo el número de la línea, puede hacer

awk '{print $1}' file | sort | uniq -c | 
 while read num dupe; do [[ $num > 1 ]] && grep -n -- "$dupe" file; done | cut -d: -f 1

Y para imprimir solo la línea:

awk '{print $1}' file | sort | uniq -c | 
while read num dupe; do [[ $num > 1 ]] && grep -n -- "$dupe" file; done | cut -d: -f 2-

Explicación:

El awkscript solo imprime el primer campo separado del espacio del archivo. Use $Npara imprimir el enésimo campo. sortlo ordena y uniq -ccuenta las ocurrencias de cada línea.

Esto luego se pasa al whilebucle que guarda el número de ocurrencias como $numy la línea como $dupey si $numes mayor que uno (por lo que se duplica al menos una vez) buscará el archivo para esa línea, utilizando -npara imprimir el número de línea. El --le dice grepque lo que sigue no es una opción de línea de comando, útil para cuándo $dupepuede comenzar -.


1

Sin duda, el más detallado de la lista, probablemente podría ser más corto:

#!/usr/bin/python3
import collections
file = "file.txt"

def find_duplicates(file):
    with open(file, "r") as sourcefile:
        data = sourcefile.readlines()
    splitlines = [
        (index, data[index].split("  ")) for index in range(0, len(data))
        ]
    lineheaders = [item[1][0] for item in splitlines]
    dups = [x for x, y in collections.Counter(lineheaders).items() if y > 1]
    dupsdata = []
    for item in dups:
        occurrences = [
            splitlines_item[0] for splitlines_item in splitlines\
                       if splitlines_item[1][0] == item
            ]
        corresponding_lines = [
            "["+str(index)+"] "+data[index] for index in occurrences
            ]
        dupsdata.append((occurrences, corresponding_lines))

    # printing output   
    print("found duplicates:\n"+"-"*17)
    for index in range(0, len(dups)):
        print(dups[index], dupsdata[index][0])
        lines = [item for item in dupsdata[index][1]]
        for line in lines:
            print(line, end = "")


find_duplicates(file)

da en un archivo de texto como:

monkey  banana
dog  bone
monkey  banana peanut
cat  mice
dog  cowmeat

una salida como:

found duplicates:
-----------------
dog [1, 4]
[1] dog  bone
[4] dog  cowmeat
monkey [0, 2]
[0] monkey  banana
[2] monkey  banana peanut

Una vez que haya elegido las líneas para eliminar:

removelist = [2,1]

def remove_duplicates(file, removelist):
    removelist = sorted(removelist, reverse=True)
    with open(file, "r") as sourcefile:
        data = sourcefile.readlines()
    for index in removelist:
        data.pop(index)
    with open(file, "wt") as sourcefile:
        for line in data:
            sourcefile.write(line)

remove_duplicates(file, removelist)

0

Ver lo siguiente ordenado file.txt:

addons.mozilla.org/en-US/firefox/addon/click-to-play-per-element/ ::: C2P per-element
addons.mozilla.org/en-us/firefox/addon/prospector-oneLiner/ ::: OneLiner
askubuntu.com/q/21033 ::: What is the difference between gksudo and gksu?
askubuntu.com/q/21148 ::: openoffice calc sheet tabs (also askubuntu.com/q/138623)
askubuntu.com/q/50540 ::: What is Ubuntu's Definition of a "Registered Application"?
askubuntu.com/q/53762 ::: How to use lm-sensors?
askubuntu.com/q/53762 ::: how-to-use-to-use-lm-sensors
stackoverflow.com/q/4594319 ::: bash - shell replace cr\lf by comma
stackoverflow.com/q/4594319 ::: shell replace cr\lf by comma
wiki.ubuntu.com/ClipboardPersistence ::: ClipboardPersistence
wiki.ubuntu.com/ClipboardPersistence ::: ClipboardPersistence - Ubuntu Wiki
www.youtube.com/watch?v=1olY5Qzmbk8 ::: Create new mime types in Ubuntu
www.youtube.com/watch?v=2hu9JrdSXB8 ::: Change mouse cursor
www.youtube.com/watch?v=Yxfa2fXJ1Wc ::: Mouse cursor size

Debido a que la lista es corta, puedo ver (después de ordenar) que hay tres conjuntos de duplicados.

Entonces, por ejemplo, puedo elegir mantener:

askubuntu.com/q/53762 ::: How to use lm-sensors?

más bien que

askubuntu.com/q/53762 ::: how-to-use-to-use-lm-sensors

Pero para una lista más larga esto será difícil. Basado en las dos respuestas, una que sugiere uniqy la otra que sugiere cut, encuentro que este comando me da el resultado que me gustaría:

$ cut -d " " -f1 file.txt | uniq -d
askubuntu.com/q/53762
stackoverflow.com/q/4594319
wiki.ubuntu.com/ClipboardPersistence
$

He actualizado mi respuesta con otra variante de cut. Si está realizando un trabajo de eliminación de duplicados, los números de línea pueden ser muy útiles. Para imprimir todos los duplicados, use la -Dopción en lugar de -d.
Lekensteyn

Creo que es mejor usarlo: for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n $dup file.txt; donecomo en mi respuesta. Le dará una mejor vista previa de lo que le interesa.
Radu Rădeanu

0

Ella es como lo resolví:

file_with_duplicates:

1,a,c
2,a,d
3,a,e <--duplicate
4,a,t
5,b,k <--duplicate
6,b,l
7,b,s
8,b,j
1,b,l
3,a,d <--duplicate
5,b,l <--duplicate

Archivo ordenado y deducido por las columnas 1 y 2:

sort -t',' -k1,1 -k2,2 -u file_with_duplicates

Archivo ordenado solo por las columnas 1 y 2:

sort -t',' -k1,1 -k2,2 file_with_duplicates

Mostrar solo la diferencia:

diff <(sort -t',' -k1,1 -k2,2 -u file_with_duplicates) <(sort -t',' -k1,1 -k2,2 file_with_duplicates)

 3a4
   3,a,d
 6a8
   5,b,l
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.