Як видалити повторювані рядки всередині текстового файлу?


126

Величезний (до 2 ГБ) мій текстовий файл містить близько 100 точних дублікатів кожного рядка в ньому (в моєму випадку непотрібний, оскільки файл - це таблиця даних, схожа на CSV).

Що мені потрібно - це видалити всі повтори, при цьому (бажано, але це може бути пожертвовано для значного підвищення продуктивності), підтримуючи початковий порядок послідовностей. В результаті кожен рядок повинен бути унікальним. Якщо було 100 рівних рядків (як правило, дублікати розводяться по файлу і не будуть сусідами), залишиться лише один із виду.

Я написав програму в Scala (вважайте Java, якщо ви не знаєте про Scala) для її реалізації. Але, можливо, існують швидші рідні інструменти, написані на C, які здатні це зробити швидше?

ОНОВЛЕННЯ: awk '!seen[$0]++' filenameрішення, здавалося, спрацювало нормально, доки файли були біля 2 Гб або менше, але тепер, коли я прибираю файл з 8 Гб, він більше не працює. Здається, що безліч нескінченностей на Mac із 4-гігабайтною оперативною пам’яттю та 64-розрядному ПК Windows 7 з 4 гігабайтами оперативної пам’яті та підмінами 6 GiB просто не вистачає пам'яті. І я не відчуваю ентузіазму пробувати це на Linux з 4-мегабайтною оперативною пам’яттю, враховуючи цей досвід.


це знищить ваше замовлення, але, ви спробували сортувати -у, я не маю уявлення, як чи якщо він може працювати на такому масивному файлі
0x7c0

5
C часто не є значно швидшим, ніж Java, і якщо ви запускаєте його (на порядку) зараз, є великий шанс, що він закінчиться, перш ніж ви отримаєте відповідь тут, реалізуйте його, і він закінчить працювати; не в порядку, sort -uшвидше за все, буде швидше.
Кевін

Відповіді:


214

awkРішення видно на #bash (Freenode):

awk '!seen[$0]++' filename

1
Просто спробував це на 2G-файлі, і на моєму ноутбуці пішло три хвилини. Непогано. Я також спробував ім'я файлу uniq | awk "! бачив [$ 0] ++", але це не було швидше.
mgjk

Це напрочуд швидше, ніж більш деталізована awkверсія, що використовує 2 пошукові масиви (показані як розширене пояснення у відповіді Гілла): 0m36.132s проти 0m49.958s .. на 50 мільйонів рядків .. Я думав, що вузьким місцем буде I / O, але додатковий пошук масиву - це 1 мільйон елементів у масиві, здається, досить значне вм'ятина ...
Peter.O

Але як це порівнювати з сортуванням -у ....?
HashWizard

1
@HashWizard: ця команда не сортує, а усуває кожне наступне виникнення того самого рядка
enzotib

1
@MaxWilliams так, це працює, якщо вони розподіляються випадковим чином.
setholopolus

47

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

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

Якщо всі рядки починаються з символу, який не є пробілом, ви можете відмовитися від деяких варіантів:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

Для великої кількості дублювання метод, який вимагає збереження лише однієї копії кожного рядка в пам'яті, буде працювати краще. З деякими інтерпретаційними накладними відомостями, існує дуже стислий сценарій awk для цього (вже розміщений enzotib ):

<input awk '!seen[$0]++'

Менш стисло: !seen[$0] {print} {seen[$0] += 1}тобто надрукуйте поточний рядок, якщо він ще не був помічений, тоді збільште seenлічильник для цього рядка (неініціалізовані змінні або елементи масиву мають числове значення 0).

Для довгих рядків ви можете зберегти пам'ять, зберігаючи лише контрольну суму (наприклад, криптографічний дайджест) кожного рядка. Наприклад, використовуючи SHA-1, вам потрібно лише 20 байт плюс постійний накладний набір на рядок. Але обчислення дайджестів відбувається досить повільно; цей метод виграє лише в тому випадку, якщо у вас швидкий процесор (особливо той, який має апаратний прискорювач для обчислення дайджестів) і не багато пам'яті щодо розміру файлу та достатньо довгих рядків. Жодна основна утиліта не дозволяє обчислити контрольну суму для кожного рядка; вам доведеться нести накладну інтерпретацію Perl / Python / Ruby / ... або написати спеціально складену програму.

<input perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output

@Gilles Виходячи з вашого пояснення awk '!seen[$0]++', чи означає це, що якщо awk бачить 2 повторюваних рядка, він буде зберігати завжди перший і ігнорувати всі наступні? (Або він збереже останній?)
user779159

1
@ user779159 Він зберігає перше: кожен рядок введення або надрукується негайно (перше виникнення), або зовсім не (повторення).
Жиль

Але як це порівнювати з сортуванням -у ...?
HashWizard

@HashWizard Звичайна sort -uзміна порядку. Моя відповідь показує рішення, які зберігають порядок (порядок перших подій, якщо бути точним).
Жиль

@Gilles Ви б сказали, що це швидше, ніж сортувати -u для великих файлів (10G) з 50% -ними повторами?
HashWizard

25
sort -u big-csv-file.csv > duplicates-removed.csv

Зверніть увагу, що вихідний файл буде сортований.


1
Не так швидко, як awkкоманда в інших відповідях, але концептуально проста!
Йоганн

@Johann Я роблю це досить часто для файлів із сотнями тисяч (навіть мільйон) коротких рядків, що закінчуються в новому рядку. Я отримую результати досить швидко для експериментів, які роблю. Це може бути важливішим, якщо використовувати його в сценаріях, які запускаються знову і знову, економія часу може бути значною.
Владислав Довгалеч

1
Використовуйте sort -uдля видалення дублікатів під час сортування, а не після. (І економить пропускну здатність пам'яті), передаючи його іншій програмі). Це краще, ніж awkверсія, якщо ви також хочете, щоб ваш вихід був відсортований. (ОП у цьому питанні хоче, щоб його оригінальне замовлення було збережене , тому це хороша відповідь на дещо інший випадок використання.)
Пітер Кордес,

Для мене знадобилося близько хвилини для 5,5 мільйона рядкових файлів (1,8 ГБ загалом). Блискуча.
Макс Вільямс

18

Якщо припустити, що ви можете дозволити собі зберегти стільки, скільки дебюльований файл у пам’яті (якщо ваші дані дійсно дублюються з коефіцієнтом 100, це повинно бути приблизно 20 Мбіт + накладні витрати), ви можете зробити це дуже легко за допомогою Perl.

$ perl -ne 'print unless $dup{$_}++;' input_file > output_file

Це також зберігає порядок.

Ви можете отримати кількість зустрічей кожного рядка з %dupхешу, якщо цього хочете, як додатковий безкоштовний бонус.

Якщо ви віддаєте перевагу awk, це теж слід робити (така ж логіка, що і версія perl, те саме впорядкування, ті ж дані, зібрані у dupзмінній):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file

Це занадто добре @Mat, я збирався прорізати файл, lol ;-).
Nikhil Mulley

Тепер чекаємо @ManAtWork на його сед і awk магічне
ткацтво

дивовижно знову за підказку awk :-)
Nikhil Mulley

1
Чи можна змінити сценарій perl, щоб видалити лише повторювані сусідні рядки?
dumbledad

2
@dumbledad: uniqчи це все само собою
Мат

3

Оскільки жодна інша відповідь не надається на місці підтримки, ось одна:

gawk -i inplace '!a[$0]++' file

Чи зберігає це порядок? До речі, це для мене не вийшло. Моя версія така:GNU Awk 4.0.2
Леонід

1
@ Леонід так, так і є. Він друкує перше виникнення будь-якої унікальної лінії. Підтримка inplace була вперше представлена ​​у версії 4.1, яка вийшла у 2013 році.
Ян Чрен - rindeal

3

Ви можете використовувати uniq http://www.computerhope.com/unix/uuniq.htm

uniq звіти або фільтрує повторні рядки у файлі.


Коли ви даєте відповідь, бажано дати пояснення щодо того, ЧОМУ ваша відповідь . Отже, чим ця відповідь відрізняється від кількох попередніх відповідей?
Стівен Рауч

1
Зі сторінки manq uniq: Примітка. 'uniq' does not detect repeated lines unless they are adjacent. Отже, ви повинні спочатку відсортувати її та втратити порядок не повторюваних рядків.
Віндолін

2

Лайнери Python One:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile

це призводить до того, що весь файл буде прихованим до пам'яті і, можливо, не підходить для проблеми ОП. Також не гарантується збереження замовлення
iruvar

Дякую за пропозицію, я щойно вивчав пітон .. просто спробував це з метою навчання .. :)
Рахул Патіл

Ось версія Python 2.7, яка не є однолінійною, але (лаконічно) повертає унікальні рядки, зберігаючи порядок, не завантажуючи в файл увесь файл або створюючи єдиний гігантський рядок для подачі для друку
iruvar

Спасибі @ 1_CR Я сьогодні чомусь навчився :)OrderedDict
Рахул Патіль

0

Жодна з відповідей тут не працювала на моєму Mac, тому я написав простий скрипт python, який працює для мене. Я ігнорую пробіли проміжних / кінцевих пробілів, а також не дбаю про споживання пам'яті.

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

Збережіть вищезазначене в unique.py і запустіть так:

python unique.py inputfile.txt outputfile.txt

-1

З bash 4 можна використовувати чисто-баш-рішення, яке використовує переваги асоціативних масивів . Ось приклад

unset llist; declare -A llist;
while read -r line; do
if [[ ${llist[$line]} ]]; then
  continue
else 
  printf '%s\n' "$line"
  llist[$line]="x"
fi
done < file.txt

2
Не використовуйте readпетлі для обробки великих текстових файлів. bash повинен читати один байт одночасно, щоб уникнути перенавантаження нового рядка. Bash також не дуже швидкий в обробці тексту взагалі порівняно з awk. Якщо ви все-таки користуєтесь цим, read -raуникнете поїдання зворотних нахилів у вашому введенні. Крім того , не забудьте unset llist після циклу, якщо ви помістіть це в функції оболонки або використовувати його в інтерактивному режимі .
Пітер Кордес

2
@PeterCordes, або ви могли просто посилатися на це :-)
iruvar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.