Zidentyfikować zduplikowane linie w pliku bez ich usuwania?

11

Mam swoje referencje jako plik tekstowy z długą listą wpisów, a każda z nich ma dwa (lub więcej) pól.

Pierwsza kolumna to adres URL odwołania; druga kolumna to tytuł, który może się nieco różnić w zależności od tego, w jaki sposób dokonano wpisu. To samo dla trzeciego pola, które może być obecne lub nie.

Chcę zidentyfikować, ale nie usunąć wpisów, które mają identyczne pierwsze pole (URL referencyjny). Wiem o sort -k1,1 -utym, ale to automatycznie (nieinteraktywnie) usunie wszystkie oprócz pierwszego trafienia. Czy jest jakiś sposób, aby poinformować mnie, żebym mógł wybrać, który zachować?

W poniższym wyciągu z trzech linii, które mają to samo pierwsze pole ( http://unix.stackexchange.com/questions/49569/), chciałbym zachować linię 2, ponieważ ma ona dodatkowe tagi (sort, CLI) i usuwa linie nr 1 i nr 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

Czy istnieje program ułatwiający identyfikację takich „duplikatów”? Czy mogę ręcznie wyczyścić, usuwając osobiście wiersze nr 1 i nr 3?

DK Bose
źródło
Nie do końca rozumiem twój przykład ... czy możesz podać bardziej uproszczoną wersję danych wejściowych i oczekiwanych wyników?
Oli
Sprawdź, czy teraz jest jaśniej?
DK Bose

Odpowiedzi:

9

Jeśli rozumiem twoje pytanie, myślę, że potrzebujesz czegoś takiego:

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

lub:

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

gdzie file.txttwój plik zawierający dane o tobie są zainteresowani.

Na wyjściu zobaczysz liczbę linii i linii, w których pierwsze pole znajduje się dwa lub więcej razy.

Radu Rădeanu
źródło
3
Dziękuję: nawet cut -d " " -f1 file.txt | uniq -ddaje mi niezły wynik.
DK Bose
@DKBose Prawdopodobnie jest więcej możliwości, ale chciałem skorzystać z twojego polecenia.
Radu Rădeanu,
Dzięki. Drugie polecenie jest tym, które lubię. Możesz usunąć pierwszy. A jeśli wyjaśnisz kod, to też byłoby miło :)
DK Bose
10

Jest to klasyczny problem, który można rozwiązać za pomocą uniqpolecenia. uniqmoże wykrywać duplikaty kolejnych linii i usuwać duplikaty ( -u, --unique) lub zachować tylko duplikaty ( -d, --repeated).

Ponieważ zamawianie zduplikowanych linii nie jest dla Ciebie ważne, powinieneś je najpierw posortować. Następnie użyj, uniqaby wydrukować tylko unikalne linie:

sort yourfile.txt | uniq -u

Istnieje również opcja -c( --count), która drukuje liczbę duplikatów tej -dopcji. Szczegółowe informacje można znaleźć na stronie podręcznika użytkownika uniq.


Jeśli naprawdę nie przejmujesz się częściami po pierwszym polu, możesz użyć następującego polecenia, aby znaleźć duplikaty kluczy i wydrukować dla nich każdy numer wiersza (dodaj kolejny, | sort -naby sortować dane wyjściowe według wiersza):

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

Ponieważ chcesz zobaczyć zduplikowane linie (używając pierwszego pola jako klucza), nie możesz bezpośrednio użyć uniq. Problemem utrudniającym automatyzację jest to, że części tytułowe różnią się, ale program nie może automatycznie określić, który tytuł należy uznać za ostateczny.

Oto skrypt AWK (zapisz go script.awk), który pobiera plik tekstowy jako dane wejściowe i drukuje wszystkie zduplikowane linie, abyś mógł zdecydować, który plik usunąć. ( 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];
            }
        }
    }
}
Lekensteyn
źródło
Myślę, że jest to bliskie temu, czego chcę, ale potrzebuję przeciwieństwa `-f, --skip-fields = N (unikaj porównywania pierwszych N pól). Innymi słowy, chcę wziąć pod uwagę tylko pierwsze pole, adresy URL.
DK Bose
@DKBose Istnieje opcja -w( --check-chars) ograniczenia do określonej liczby znaków, ale na przykładzie masz zmienne pierwsze pola. Ponieważ uniqnie obsługuje wyboru pola, musisz użyć obejścia. Podam przykład AWK, ponieważ jest to łatwiejsze.
Lekensteyn
Tak, właśnie patrzyłem, -wale długość pierwszego pola jest zmienna :(
DK Bose
@DKBose Proszę zobaczyć najnowszą edycję
Lekensteyn
1
Dostaję awk: script.awk: wiersz 4: błąd składniowy w pobliżu lub w pobliżu [awk: script.awk: wiersz 10: błąd składniowy w pobliżu lub w pobliżu [awk: script.awk: wiersz 18: błąd składniowy w pobliżu lub blisko}
DK Bose
2

Jeśli dobrze to przeczytam, wystarczy coś takiego

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

Spowoduje to wydrukowanie numeru linii zawierającej duplikat i samej linii. Na przykład za pomocą tego pliku:

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

Wyprodukuje ten wynik:

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

Aby wydrukować tylko numer linii, możesz to zrobić

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

I aby wydrukować tylko linię:

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

Wyjaśnienie:

awkSkrypt po prostu drukuje 1st przestrzeń oddziela pole pliku. Służy $Ndo drukowania N-tego pola. sortsortuje je i uniq -cliczy wystąpienia każdej linii.

Jest on następnie przekazywany do whilepętli, która zapisuje liczbę wystąpień as, $numa linia as $dupei if $numjest większa niż jeden (więc jest zduplikowana co najmniej raz), przeszuka plik dla tej linii, używając -ndo wydrukowania numeru linii. --Mówi grep, że co za tym idzie nie jest to opcja wiersza polecenia, przydatne do kiedy $dupemożna zacząć -.

terdon
źródło
1

Bez wątpienia najbardziej wyczerpujący na liście może być prawdopodobnie krótszy:

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

daje plik tekstowy taki jak:

monkey  banana
dog  bone
monkey  banana peanut
cat  mice
dog  cowmeat

wyjście takie jak:

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

Po wybraniu linii do usunięcia:

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)
Jacob Vlijm
źródło
0

Zobacz następujące posortowane 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

Ponieważ lista jest krótka, po sortowaniu widzę, że istnieją trzy zestawy duplikatów.

Następnie mogę na przykład zachować:

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

zamiast

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

Ale w przypadku dłuższej listy będzie to trudne. Na podstawie dwóch odpowiedzi, z których jedna sugeruje, uniqa druga sugeruje cut, stwierdzam, że to polecenie daje mi wynik, który chciałbym:

$ cut -d " " -f1 file.txt | uniq -d
askubuntu.com/q/53762
stackoverflow.com/q/4594319
wiki.ubuntu.com/ClipboardPersistence
$
DK Bose
źródło
Zaktualizowałem swoją odpowiedź o inny wariant cut. Jeśli wykonujesz pracę polegającą na usuwaniu duplikatów, numery linii mogą być bardzo pomocne. Aby wydrukować wszystkie duplikaty, użyj -Dopcji zamiast -d.
Lekensteyn
Myślę, że lepiej użyj: for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n $dup file.txt; donejak w mojej odpowiedzi. To da ci lepszy podgląd tego, co Cię interesuje.
Radu Rădeanu,
0

Właśnie tak to rozwiązałam:

plik_z duplikatami:

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

Plik posortowany i deduponowany według kolumn 1 i 2:

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

Plik posortowany tylko według kolumn 1 i 2:

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

Pokaż tylko różnicę:

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
Clint Smith
źródło