Identificare le linee duplicate in un file senza eliminarle?


11

Ho i miei riferimenti come file di testo con un lungo elenco di voci e ognuno ha due (o più) campi.

La prima colonna è l'URL del riferimento; la seconda colonna è il titolo che può variare leggermente a seconda di come è stata effettuata la voce. Lo stesso vale per il terzo campo che può essere presente o meno.

Voglio identificare ma non rimuovere le voci che hanno il primo campo (url di riferimento) identico. Lo so, sort -k1,1 -uma questo rimuoverà automaticamente (in modo non interattivo) tutto tranne il primo colpo. C'è un modo per farmelo sapere in modo che io possa scegliere quale conservare?

Nell'estratto di seguito di tre righe che hanno lo stesso primo campo ( http://unix.stackexchange.com/questions/49569/), vorrei mantenere la riga 2 perché ha tag aggiuntivi (ordinamento, CLI) ed elimina le righe n. 1 e n. 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

Esiste un programma per aiutare a identificare tali "duplicati"? Quindi, posso ripulire manualmente eliminando personalmente le righe n. 1 e n. 3?


Non capisco bene il tuo esempio ... potresti dare una versione più semplificata dell'input e dell'output previsto?
Oli

Vedi ora se è più chiaro?
DK Bose,

Risposte:


9

Se capisco la tua domanda, penso che tu abbia bisogno di qualcosa come:

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

dove file.txtè il tuo file contenente dati su di te sono interessati.

Nell'output verrà visualizzato il numero di righe e righe in cui viene trovato il primo campo due o più volte.


3
Grazie: cut -d " " -f1 file.txt | uniq -dmi dà anche un bel risultato.
DK Bose,

@DKBose Probabilmente ci sono più possibilità, ma volevo usare anche il tuo comando.
Radu Rădeanu,

Grazie. Il secondo comando è quello che mi piace. Puoi rimuovere il primo. E se spieghi il codice sarebbe bello anche :)
DK Bose

10

Questo è un problema classico che può essere risolto con il uniqcomando. uniqpuò rilevare righe consecutive duplicate e rimuovere duplicati ( -u, --unique) o conservare solo duplicati ( -d, --repeated).

Poiché l'ordinamento di righe duplicate non è importante per te, è necessario prima ordinarlo. Quindi utilizzare uniqper stampare solo linee uniche:

sort yourfile.txt | uniq -u

C'è anche un'opzione -c( --count) che stampa il numero di duplicati per l' -dopzione. Vedere la pagina di manuale di uniqper i dettagli.


Se davvero non ti interessano le parti dopo il primo campo, puoi usare il seguente comando per trovare chiavi duplicate e stampare ogni numero di riga per esso (aggiungine un altro | sort -nper ordinare l'output per riga):

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

Poiché vuoi vedere righe duplicate (usando il primo campo come chiave), non puoi usare direttamente uniq. Il problema che rende difficile l'automazione è che le parti del titolo variano, ma un programma non può determinare automaticamente quale titolo debba essere considerato quello finale.

Ecco uno script AWK (salvalo in script.awk) che accetta il tuo file di testo come input e stampa tutte le righe duplicate in modo da poter decidere quale eliminare. ( 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];
            }
        }
    }
}

Penso che questo sia vicino a quello che voglio, ma ho bisogno del contrario di `-f, --skip-fields = N (evita di confrontare i primi N campi). In altre parole, voglio considerare solo il primo campo, gli URL.
DK Bose,

@DKBose Esiste un'opzione -w( --check-chars) per limitare a un numero fisso di caratteri, ma vedendo il tuo esempio, hai i primi campi variabili. Poiché uniqnon supporta la selezione dei campi, è necessario utilizzare una soluzione alternativa. Includerò un esempio AWK poiché è più facile.
Lekensteyn,

Sì, stavo solo guardando -wma la lunghezza del primo campo è variabile :(
DK Bose

@DKBose Vedi l'ultima modifica
Lekensteyn,

1
Sto ottenendo awk: script.awk: linea 4: errore di sintassi ao vicino [awk: script.awk: linea 10: errore di sintassi ao vicino [awk: script.awk: linea 18: errore di sintassi ao vicino}
DK Bose,

2

Se lo leggo correttamente, tutto ciò che serve è qualcosa di simile

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

Ciò stamperà il numero della riga che contiene il duplicato e la riga stessa. Ad esempio, usando questo file:

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

Produrrà questo output:

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

Per stampare solo il numero della linea, potresti farlo

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

E per stampare solo la linea:

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

Spiegazione:

Lo awkscript stampa solo il primo campo separato da spazio del file. Utilizzare $Nper stampare l'ennesimo campo. sortordina e uniq -cconta le occorrenze di ogni riga.

Questo viene quindi passato al whileciclo che salva il numero di occorrenze come $nume la riga come $dupee se $numè maggiore di una (quindi è duplicata almeno una volta) cercherà quella riga nel file, usando -nper stampare il numero di riga. Il --dice grepche ciò che segue non è una linea di comando, utile per quando $dupepuò iniziare con -.


1

Senza dubbio il più dettagliato nell'elenco, potrebbe essere probabilmente più breve:

#!/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)

dà un file di testo come:

monkey  banana
dog  bone
monkey  banana peanut
cat  mice
dog  cowmeat

un output come:

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

Dopo aver selezionato le righe da rimuovere:

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

Vedi i seguenti ordinati 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

Poiché l'elenco è breve, posso vedere (dopo l'ordinamento) che ci sono tre serie di duplicati.

Quindi, ad esempio, posso scegliere di mantenere:

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

piuttosto che

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

Ma per un elenco più lungo questo sarà difficile. Sulla base delle due risposte suggerite una uniqe l'altra cut, trovo che questo comando mi dia l'output che vorrei:

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

Ho aggiornato la mia risposta con un'altra variante di cut. Se stai eseguendo un lavoro di deduplicazione, i numeri di riga potrebbero essere molto utili. Per stampare tutti i duplicati, utilizzare l' -Dopzione anziché -d.
Lekensteyn,

Penso che ti serva meglio: for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n $dup file.txt; donecome nella mia risposta. Ti darà una migliore anteprima di ciò che ti interessa.
Radu Rădeanu,

0

Ecco come l'ho risolto:

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

File ordinato e dedotto dalle colonne 1 e 2:

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

File ordinato solo per le colonne 1 e 2:

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

Mostra solo la differenza:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.