Визначити дублікати рядків у файлі, не видаляючи їх?


11

У мене є посилання як текстовий файл з довгим списком записів, і кожен має два (або більше) поля.

Перший стовпець - URL-адреса посилання; другий стовпець - це заголовок, який може дещо відрізнятися залежно від способу внесення. Те саме для третього поля, яке може бути, а може і не бути.

Я хочу визначити, але не видаляти записи, у яких перше поле (URL-адреса посилання) однакове. Я знаю про це, sort -k1,1 -uале це автоматично (неінтерактивно) видалить усі, крім першого звернення. Чи є спосіб просто повідомити мене, щоб я міг вибрати, який зберегти?

У витязі нижче трьох рядків, що мають одне і те ж перше поле ( http://unix.stackexchange.com/questions/49569/), я хотів би зберегти рядок 2, оскільки він містить додаткові теги (сортування, CLI) та видалення рядків №1 та №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

Чи існує програма, яка допоможе виявити такі "дублікати"? Тоді я можу вручну очистити, особисто видаливши рядки №1 та №3?


Я не зовсім розумію ваш приклад ... ви могли б дати більш спрощену версію введення та очікуваний вихід?
Олі

Будь ласка, подивіться, чи зрозуміліше зараз?
ДК Бозе

Відповіді:


9

Якщо я розумію ваше запитання, я думаю, що вам потрібно щось на зразок:

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

або:

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

де file.txtваш файл, що містить дані про вас, що вас цікавлять.

На виході ви побачите кількість рядків і рядків, де перше поле знайдено два чи більше разів.


3
Дякую: навіть cut -d " " -f1 file.txt | uniq -dдає мені гарний результат.
ДК Бозе

@DKBose Можливо, можливо більше можливостей, але я хотів використати і вашу команду.
Radu Rădeanu

Спасибі. Друга команда - це те, що мені подобається. Перший можна видалити. І якщо ви поясните код, який би також був непоганий :)
ДК Bose

10

Це класична проблема, яку можна вирішити за допомогою uniqкоманди. uniqможе виявляти повторювані послідовні рядки та видаляти дублікати ( -u, --unique) або зберігати лише дублікати ( -d, --repeated).

Оскільки впорядкування повторюваних рядків для вас не важливо, спершу слід упорядкувати його. Тоді використовуйте uniqдля друку лише унікальні рядки:

sort yourfile.txt | uniq -u

Існує також опція -c( --count), яка друкує кількість дублікатів для -dопції. Детальніше див. На сторінці керівництва uniq.


Якщо ви дійсно не переймаєтесь деталями після першого поля, ви можете скористатися наступною командою, щоб знайти дублюючі ключі та надрукувати кожен номер рядка для цього (додайте інший, | sort -nщоб вихід був відсортований за рядком):

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

Оскільки ви хочете бачити повторювані рядки (використовуючи перше поле як ключ), ви не можете безпосередньо використовувати їх uniq. Проблема, яка ускладнює автоматизацію, полягає в тому, що частини заголовків змінюються, але програма не може автоматично визначити, який заголовок слід вважати остаточним.

Ось сценарій AWK (збережіть його script.awk), який приймає ваш текстовий файл як вхідний і друкує всі повторювані рядки, щоб ви могли вирішити, який видалити. ( 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];
            }
        }
    }
}

Я думаю, що це близько до того, що я хочу, але мені потрібно протилежне `-f, --skip-polja = N (уникайте порівнювання перших N полів). Іншими словами, я хочу, щоб тільки перше поле, URL-адреси, було розглянуто.
ДК Бозе

@DKBose Є варіант -w( --check-chars), щоб обмежити фіксовану кількість символів, але, побачивши ваш приклад, у вас є перші змінні поля. Оскільки uniqне підтримується вибір поля, вам доведеться скористатися способом вирішення. Я включу приклад AWK, оскільки це простіше.
Лекенштейн

Так, я просто дивився, -wале довжина першого поля мінлива :(
DK Bose

@DKBose Перегляньте останню
редакцію

1
Я отримую awk: script.awk: рядок 4: помилка синтаксису в або поблизу [awk: script.awk: рядок 10: помилка синтаксису в або поблизу [awk: script.awk: рядок 18: синтаксична помилка в або біля}
DK Bose

2

Якщо я прочитав це правильно, все, що вам потрібно, - це щось на кшталт

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

Це дозволить роздрукувати номер рядка, який містить дуп і саму лінію. Наприклад, використовуючи цей файл:

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

Це дасть такий вихід:

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

Щоб надрукувати лише номер рядка, ви могли б це зробити

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

І надрукувати лише рядок:

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

Пояснення:

awkСценарій просто друкує перше місце , відокремлене поле файлу. Використовуйте $Nдля друку N-го поля. sortсортує його та uniq -cпідраховує входження кожного рядка.

Потім він передається до whileциклу, який зберігає кількість входів як $numі рядок, як $dupeі якщо $numбільше, ніж одиниця (тож дублюється хоча б один раз), він шукатиме файл у цьому рядку, використовуючи -nдля друку номер рядка. --Каже grep, що слід не параметр командного рядка, корисно , коли $dupeможна почати з -.


1

Без сумніву, найголовніший у списку, ймовірно, може бути коротшим:

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

надає текстовий файл на зразок:

monkey  banana
dog  bone
monkey  banana peanut
cat  mice
dog  cowmeat

вихід типу:

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

Вибравши рядки для видалення:

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

Дивіться такі сортовані 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

Оскільки список короткий, я можу побачити (після сортування), що є три набори дублікатів.

Тоді, наприклад, я можу вибрати, щоб зберегти:

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

а не

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

Але для більш тривалого переліку це буде складно. Грунтуючись на двох відповідях, одна - одна, uniqа інша cut- я вважаю, що ця команда дає мені результат, який я хотів би:

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

Я оновив свою відповідь ще одним варіантом cut. Якщо ви виконуєте роботу з дублювання, номери рядків можуть бути дуже корисними. Щоб надрукувати всі дублікати, використовуйте -Dопцію замість -d.
Лекенштейн

Я думаю, вам краще скористатися: for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n $dup file.txt; doneяк у моїй відповіді. Це дасть вам кращий попередній перегляд того, що вас цікавить.
Radu Rădeanu

0

Її я так вирішив:

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

Файл відсортовано та виведено стовпцями 1 та 2:

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

Файл, відсортований лише за стовпцями 1 та 2:

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

Показати лише різницю:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.