Удалите повторяющиеся строки, сохраняя порядок строк


14
[root@server]# awk '!seen[$0]++' out.txt > cleaned
awk: (FILENAME=out.txt FNR=8547098) fatal error: internal error
Aborted
[root@server]#

«Сервер» имеет: 8 ГБ ОЗУ + 16 ГБ SWAP, x> 300 ГБ свободного места, amd64, настольный ЦП. Научный Linux 6.6. На нем больше ничего не работает, чтобы сделать LOAD. Awk прерывается через несколько секунд .. out.txt составляет ~ 1,6 ГБайт. GNU Awk 3.1.7.

Вопрос : Как я могу удалить дубликаты строк, сохраняя порядок строк? Случай также важен, например: «А» и «а» - это две разные строки, должны их сохранить. Но «а» и «а» дублируют, нужен только первый.

Ответ может быть что угодно .. если awk не подходит для этого .. тогда perl / sed .. в чем может быть проблема?

[root@server]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61945
max locked memory       (kbytes, -l) 99999999
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 99999999
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61945
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
[root@server]# 

Обновление: я пробовал это на машине RHEL, она не прерывается, но у меня не было времени ждать, пока она закончится .. почему SL linux отличается от RHEL?

Обновление: я пытаюсь на Ubuntu 14 виртуальных гостей ... пока это работает! Это не проблема ulimit: mawk 1.3.3

root@asdf-VirtualBox:~# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 51331
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 51331
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
root@asdf-VirtualBox:~# 

2
В вашем примере нет повторяющихся строк ...?
mikeserv

1
Какие awkверсии в двух машинах?
cuonglm

последние версии rhel и sl linux, не знаю версию rhel .. sl это: GNU Awk 3.1.7
somelooser28533

Насколько большой out.txt? Работает ли та же команда, если вы попробуете ее на меньшем файле? Сколько пользователей на машине? Было ли достаточно памяти для процесса? Что-нибудь особенное в строке 8547098 входного файла?
Terdon

Ответы:


22

Я сомневаюсь, что это будет иметь значение, но, на всякий случай, вот как сделать то же самое в Perl:

perl -ne 'print if ++$k{$_}==1' out.txt

Если проблема заключается в сохранении уникальных строк в памяти, это будет иметь ту же проблему, что и awkвы. Итак, другой подход может быть:

cat -n out.txt | sort -k2 -k1n  | uniq -f1 | sort -nk1,1 | cut -f2-

Как это устроено:

  1. В системе GNU, cat -nперед каждой строкой будет добавляться номер строки после некоторого количества пробелов, за которым следует символ <tab> . catнаправляет это входное представление в sort.

  2. sortПараметр 's' -k2дает указание рассматривать только символы от второго поля до конца строки при сортировке и sortразбивает поля по умолчанию на пробелы (или catвставленные пробелы и <tab> ) .
    Когда следует -k1n, sortсначала рассматривает 2-е поле, а затем - в случае идентичных -k2полей - 1-е поле, но сортируется численно. Таким образом, повторяющиеся строки будут отсортированы вместе, но в порядке их появления.

  3. Результаты передаются по конвейеру, uniqкоторый, как говорят, игнорирует первое поле ( -f1и также разделены пробелами), что приводит к списку уникальных строк в исходном файле и возвращается обратно sort.
  4. На этот раз sortсортирует по первому полю ( catвставленный номер строки) численно, возвращает порядок сортировки к тому, что было в исходном файле, и направляет эти результаты cut.
  5. Наконец, cutудаляет номера строк, которые были вставлены cat. Это достигается путем cutпечати только из 2-го поля до конца строки cutразделителем по умолчанию является символ <tab> ) .

Проиллюстрировать:

$ cat file
bb
aa
bb
dd
cc
dd
aa
bb
cc
$ cat -n file | sort -k2 | uniq -f1 | sort -k1 | cut -f2-
bb
aa    
dd
cc

Привет, Тердон, OP должен поддерживать порядок строк, поэтому метод cat | sort | uniq не будет работать ... Как и твоя версия на Perl ...
Ламберт,

1
Хорошее решение с sort! Но большинство из них sortмогут сделать это uniqсами, поэтому вы можете использовать шорт-сценарий sort -uk2 | sort -bk1,1n
Costas

@ Костас это больше всего sort? Я думал, что -uэто особенность GNU.
Terdon

@don_crissti ах, так и есть, спасибо. Как я мог использовать это здесь, хотя? Как я только что заметил (и отредактировал, чтобы исправить), мне нужно сначала отсортировать по 2-му полю, а затем по 1-му, чтобы сохранить порядок строк. Как я могу затем использовать -uи указать, что он должен игнорировать 1-е поле? Согласно man sort, -uэто не один из возможных вариантов -f, поэтому я не думаю, что он может быть использован здесь.
Тердон

1
это преобразование Шварца ! (+1)
Жоао

7
#!/usr/bin/perl 
use DB_File;
tie %h, 'DB_File';

while(<>){ not $h{$_} and print and $h{$_}=1 }

РЕДАКТИРОВАТЬ 1: это действительно работает? (сравнение)

Sol1 : Terdon et all Schwartzian-transform-like one-liner
    cat -n _1 | sort -uk2 | sort -nk1 | cut -f2-

Sol2 : perl  + DB_File (this answer)
    perl dbfile-uniq _1

Sol3 : PO (John W. Gill solution has a similar behavior)
    awk '!seen[$0]++' _1

Sol4: Terdon perl
    perl -ne 'print if ++$k{$_}==1' _1

Случай 1 : 100_000_000 случайных чисел (5 цифр каждое), 566 Мбайт, 31_212 различных значений:

$ while true ; do echo $RANDOM; done | head -100000000 > _1

Случай 2 : 50_000_000 случайных чисел (по 10 цифр каждый), 516 Мбайт, 48_351_464 различных значений:

$ shuf _1 |  sed 'N;s/\n/ /' > _11

(следующие цифры не очень точны):

┌────────┬────────┬────────────────┬────────┬──────┐
         Sol1    Sol2            Sol3    Sol4 
         sort...│ perl DB         awk     perl 
├────────┼────────┼────────────────┼────────┼──────┤
 case 1  6m15    6m17            0m28    0m28 
├────────┼────────┼────────────────┼────────┴──────┤
 case 2  11m15   81m44           out of memory 
├────────┼────────┼────────────────┼────────┬──────┤
 case 2          5m54 /cache=2G               
└────────┴────────┴────────────────┴────────┴──────┘

sol2 с кешем это:

use DB_File;
use Fcntl ;

$DB_HASH->{'cachesize'} = 2000_000_000;
tie %h, 'DB_File', "_my.db", O_RDWR|O_CREAT|O_TRUNC, 0640, $DB_HASH;

while(<>){ not $h{$_} and print and $h{$_}=1 }

Сортировку также можно оптимизировать, добавив опцию кэширования (не сделано).

Один быстрый вывод:

  • sort это фантастическая команда!

1
sort -uk2и sort -nk1,1разные. Первый считает от 2-го ключа до конца строки, второй рассматривает только первый ключ. Вы должны поменять свой sort -nk1там - это может быть даже быстрее, но это, безусловно, будет более надежным. Кстати, вот такие красивые коробки.
mikeserv

@mikeserv, спасибо за комментарий. Поскольку K1,1 уникален, sort -nk1 и sort -nk1,1 возвращают некоторый результат. Я пробовал оба, результат был одинаковым, и время не было отличительным.
Жоао

Это имеет смысл - спасибо за попытку, хотя. Так cat -nчто вкладка ? Я не знаю, как работает эта команда.
mikeserv

1
@mikeserv, счастливо cat -ntransfrom друг lineв spaces + the number + \t + line- формат идеально подходит для сортировки и резки
JJoao

1

Я использовал

awk -v BINMODE=rw '!($0 in a){a[$0];print}' infile >> outfile

BINMODE = rw: чтобы довольные окончания строки были довольны. (Я живу в смешанной среде ОС)

Логика проста.

Если текущей строки нет в ассоциативном массиве, добавьте ее в ассоциативный массив и напечатайте для вывода.

При таком подходе могут быть ограничения памяти. Для очень больших файлов и наборов файлов я использовал эти варианты, используя хранилище файлов, чтобы обойти ограничения.


0

Сохраняющая порядок семантика вашей задачи обладает удивительным свойством: вы можете подразделить проблему. Вы можете сделать split -l 1000000на входном файле; 1000000 строк, которые он производит, имеют лексически упорядоченные имена, что хорошо; затем уникальные кусочки; а затем (в качестве второго прохода) унифицируйте выходы тех.

Это решает проблему нехватки памяти (путем ограничения потребности в памяти) за счет превращения ее в многопроходное решение.

В частности:

Генерация входных данных:

$ cat make-uniqm-input.py
#!/usr/bin/env python
import random
n = 1000000
for i in xrange(0, n):
    print random.randint(1000, 2000)

$ python make-uniqm-input.py  > uniqm-input.txt

$ wc -l uniqm-input.txt
 1000000 uniqm-input.txt

Разделите входные данные:

$ split -l 10000 uniqm-input.txt

$ ls x?? | head
xaa
xab
xac
xad
xae
xaf
xag
xah
xai
xaj

$ ls x?? | wc -l
     100

$ cat x?? | wc -l
 1000000

Запустите uniqifier одновременно (сохранит все уникальные строки ввода в памяти):

# 'uniqm' is any order-preserving uniq implementation, such as
# gawk '!counts[$0]++'.
$ uniqm < uniqm-input.txt > output-no-splitting.txt

$ wc -l output-no-splitting.txt
    1001 output-no-splitting.txt

Запустите uniqifier для разделенных фрагментов (сохраните в памяти только уникальные строки ввода для каждого фрагмента), а затем уменьшите в качестве второго прохода:

$ for x in x??; do uniqm < $x; done | uniqm > output-with-splitting.txt

$ wc -l output-with-splitting.txt
    1001 output-with-splitting.txt

Для сравнения:

$ diff output-no-splitting.txt output-with-splitting.txt

$ head uniqm-input.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

$ head output-with-splitting.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

Я не знаю соотношения уникальных и неуникальных строк в вашем вводе, а также того, насколько хорошо смешаны входные строки - поэтому необходимо выполнить некоторые настройки с точки зрения количества необходимых вам разделенных файлов.


0

Другой подход (заслуживающий публикации в качестве отдельного ответа) заключается в следующем: вместо метода разделения файлов, который создает временные файлы, выполняйте пакетирование в самом программном обеспечении uniqifier. Например, используя реализацию Ruby uniqifier для пояснительных целей:

require 'set'
line_batch_count = 50000 # tunable parameter
lines_seen = Set.new
line_number = 0
ARGF.each do |line|
   line_number += 1
   if (line_number % line_batch_count) == 0
     lines_seen.clear
   end
   unless lines_seen.include? line
      puts line
      lines_seen << line
   end
end

Идея состоит в том, чтобы очищать хэш-наборы так часто. Тогда это становится итеративным:

$ cat uniqm-input.txt | ruby uniqm-capped.rb | wc -l
   20021

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | wc -l
    1001

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | head
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

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

Обратите внимание, что этот метод capped-uniqm не зависит от языка: вы можете очищать lines_seenмассив каждые N строк независимо от того, используете ли вы awk, python, perl, C ++ и т. Д. Для всех этих языков существуют методы set-clear; Я считаю, awkчто deleteэто нестандартно, но распространено.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.