Удаление повторяющихся записей из файла

Существует файл с несколькими идентичными и повторяющимися записями, который выглядит примерно так:

123 abc nhjk 123 abc cftr 123 abc xdrt 123 def nhjk 123 def cftr 123 def xdrt 

Если комбинация (coloumns) Field1 и Field2 соответствует, то необходимо сохранить только первое вхождение, в котором они оба совпадают. Так как 123 и abc первой строки соответствуют 123 и abc второй строки, следует сохранить только первую строку. При дальнейшем сравнении, так как совпадение истинно и для первой и третьей строки, снова нужно сохранить только первую строку.

Однако, для первой и четвертой строк 123 и 123, но abc и def не совпадают, поэтому обе строки должны быть сохранены.

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

 123 abc nhjk 123 def nhjk 

5 Solutions collect form web for “Удаление повторяющихся записей из файла”

Один из способов сделать это – с флагом -u но это может не сохранить исходный порядок файлов:

 sort -k1,1 -k2,2 -u file 

Если вам требуется удаление дубликатов с сохранением порядка файлов

 awk '!a[$1, $2]++' file 

Удивительные ответы от RobertL и 1_CR

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

 #!/bin/sh rm output.txt touch output.txt while read line do field1=$( echo $line | cut -d" " -f1) field2=$( echo $line | cut -d" " -f2) lookup="$field1 $field2" if [ -z $(grep "$lookup" output.txt) ] then echo $line >> output.txt fi done < input.txt cat output.txt exit 0 

Очевидно, его можно много сократить, но я хотел сделать каждый шаг очень ясным.

Наслаждаться.

РЕДАКТИРОВАТЬ:

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

 #!/bin/sh sort -k1,2 -u "$@" | while read line do echo "$line" done 

Мой единственный вопрос об этом – RobertL, но зачем использовать:

 sort -k1,2 -k2,2 -u 

вместо

 sort -k1,2 -u 

Согласно моему собственному тесту, ваш вид эффективен,

 $ cat robertL.sh #!/bin/sh sort -k1,1 -k2,2 -u "$@" | while read line do echo "$line" done $ time ./robertL.sh < input.txt 123 abc nhjk 123 def nhjk real 0m0.022s user 0m0.014s sys 0m0.009s 

Но второй в два раза быстрее,

 $ cat process_v2.sh #!/bin/sh sort -k1,2 -u "$@" | while read line do echo "$line" done $ time ./process_v2.sh < input.txt 123 abc nhjk 123 def nhjk real 0m0.012s user 0m0.006s sys 0m0.009s 

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

Если вам необходимо интенсивно обрабатывать каждую запись вывода, вы можете создать фильтр, который считывает каждую строку вывода. Не обрабатывайте записи внутри сортировочного / уникального алгоритма.

Оригинальный скрипт занимает около 1 секунды для обработки каждые 100 записей. Скрипт, который считывает результат сортировки, занимает менее 3/10 секунды для обработки более 380 000 записей. Для обработки этого количества данных потребовался бы первоначальный скрипт в течение одного часа .

Один час по сравнению с 3/10 секунды!

Также обратите внимание, что исходный скрипт также проводил большую часть времени в системном времени (процессы разметки, делая io и т. Д.), Еще один плохой признак проблем с производительностью.

Выполнение исходного скрипта:

  $ wc -l input.txt 1536 input.txt $ time ./jesus.sh rm: cannot remove 'output.txt': No such file or directory 123 abc nhjk 123 def nhjk real 0m16.997s #<<<--------- user 0m3.546s sys 0m16.329s #<<<--------- 

Выполнение этого сценария нового примера, только небольшая часть времени выполнения расходуется в системном коде os:

  $ time ./RobertL.sh < input.txt 123 abc nhjk 123 def nhjk real 0m0.011s #<<<--------- user 0m0.004s sys 0m0.007s #<<<--------- 

Теперь мы запускаем новый скрипт на огромном наборе данных, который, как мы знаем, займет исходный скрипт в течение 1 часа:

  $ wc -l data388440.txt 388440 data388440.txt $ time ./RobertL.sh < data388440.txt 123 abc nhjk 123 def nhjk real 0m0.282s #<<<--------- user 0m0.728s sys 0m0.032s #<<<--------- 

Новый пример скрипта:

  $ cat RobertL.sh #!/bin/sh sort -k1,1 -k2,2 -u "$@" | while read line do echo "$line" done 

Исходный сценарий, измененный для запуска без установки ksh:

  $ cat jesus.sh #!/bin/bash #!/bin/sh # does not accept [[ ... ]] #!/bin/ksh # not installed on ubuntu by default rm output.txt touch output.txt while read line do field1=$( echo $line | cut -d" " -f1) field2=$( echo $line | cut -d" " -f2) lookup="$field1 $field2" if [[ -z $(grep "$lookup" output.txt) ]] then echo $line >> output.txt fi done < input.txt cat output.txt exit 0 

Входные данные были созданы путем повторения исходных 6 строк выборочных данных, данные содержали почти все повторяющиеся записи.

Как насчет этого sed -лайнера:

 sed -n '${;p;q;};N;/^ *\([^ ][^ ]* *[^ ][^ ]*\)\( .*\)*\n *\1/{;s/\n.*//;h;G;D;};P;D' inputfile 

Это была хорошая сложная задача; благодаря! 🙂

На высоком уровне, что это делает, это итерация через входной файл, сравнивающий по две строки за раз. Если строки совпадают с первыми двумя словами, вторая строка из них отбрасывается, а следующая строка из файла берется для сравнения с первой строкой. Если строки не совпадают, первая печатается, а вторая сохраняется для сравнения с более поздними строками. Когда конец файла будет достигнут, будет напечатана строка, находящаяся в настоящее время «удерживаемая для сравнения».

Объяснение:

 -n doN't print lines by default; only if specified to print them. ${;p;q;}; if on the la$t line then Print the line and Quit. N; append a newline followed by the Next line of the file to the pattern space /^ *\([^ ][^ ]* *[^ ][^ ]*\)\( .*\)*\n *\1/ A very tricky regex: match any leading spaces, followed by a nonspace sequence, space or multiple spaces, nonspace sequence, then optionally a space followed by anything, then a newline, then any leading spaces, then the matched two words from earlier again. {; if that regex matched the pattern space, excecute the following. s/\n.*//; delete the first newline and everything after it h; copy the pattern space contents to the Hold space G; append (Get) a newline followed by the hold space contents to the pattern space D; delete everything in the pattern space up to the first newline, then start from the beginning of this sequence (with the ${ block) }; end of block. Skip to here if the tricky regex didn't match. P; Print everything in the pattern space up to the first newline. D Delete the pattern space up to the first newline. 

Обратите внимание, что вышеупомянутое очень портативно. Преднамеренно. Просто для вызова я хотел, чтобы он бежал без ? или + доступны (поскольку они не совместимы с POSIX), что делает регулярное выражение намного более утонченным.

Кроме того, логический поток не включает никаких ветвей, хотя ветви совместимы с POSIX и универсально доступны. Почему я это сделал? Это связано с тем, что не все реализации sed позволяют указывать метки в одном слое. Они требуют \ и новой строки после метки. GNU sed позволяет накладывать метки в реальном однострочном пространстве, и, например, BSD sed не делает.

Следующие два лайнера представляют собой точный эквивалент, используя GNU sed, единственное отличие в том, что они более надежны при обработке вкладок, а также пробелов:

 sed -n ':k;${;p;q;};N;/^\s*\(\S\+\s\+\S\+\)\(\s.*\)\?\n\s*\1/{;s/\n.*//;bk;};P;D' inputfile sed -n ':k;${;p;q;};N;s/^\(\s*\(\S\+\s\+\S\+\)\(\s.*\)\?\)\n\s*\2.*$/\1/;tk;P;D' inputfile 

Я в основном делал это для удовольствия. 🙂 Я думаю, что ответ 1_CR – лучший, и, конечно, он прост.

Если ваши требования становятся немного более сложными, чем в настоящее время, и его подход не будет работать, лучшим инструментом, вероятно, будет awk . Но я еще не научился awk и я узнал sed . 🙂

Если строки, которые нужно удалить, все подряд, а ключи имеют одинаковую длину, вы можете использовать:

 $ uniq --check-chars=8 <<EOF 123 abc nhjk 123 abc cftr 123 abc xdrt 123 def nhjk 123 def cftr 123 def xdrt EOF 123 abc nhjk 123 def nhjk $ 
Interesting Posts
Linux и Unix - лучшая ОС в мире.