Определить дубликаты строк в файле, не удаляя их?


11

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

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

Я хочу идентифицировать, но не удалять записи, в которых первое поле (URL ссылки) идентично. Я знаю, sort -k1,1 -uно это автоматически (не в интерактивном режиме) удалит все, кроме первого попадания. Есть ли способ просто дать мне знать, чтобы я мог выбрать, какой сохранить?

В приведенной ниже выдержке из трех строк, которые имеют одинаковое первое поле ( http://unix.stackexchange.com/questions/49569/), я хотел бы сохранить строку 2, потому что она имеет дополнительные теги (sort, 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 Возможно, есть больше возможностей, но я хотел использовать и вашу команду.
Раду Рэдяну

Благодарю. Вторая команда - та, которая мне нравится. Вы можете удалить первый. И если вы объясните код, который был бы также хорош :)
DK 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-fields = N (избегайте сравнения первых N полей). Другими словами, я хочу, чтобы учитывалось только первое поле, URL-адреса.
ДК Бозе

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

Да, я только что посмотрел, -wно длина первого поля является переменной :(
DK Bose

@DKBose Пожалуйста, смотрите последние изменения
Lekensteyn

1
Я получаю awk: script.awk: строка 4: синтаксическая ошибка в или около [awk: script.awk: строка 10: синтаксическая ошибка в или около [awk: script.awk: строка 18: синтаксическая ошибка в или около}
ДК Бозе

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как в моем ответе. Это даст вам лучший предварительный просмотр того, что вас интересует.
Раду Рэдяну

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.