Как удалить повторяющиеся строки в файле без сортировки в Unix?

137

Есть ли способ удалить повторяющиеся строки в файле в Unix?

Я могу сделать это с sort -uи uniqкоманд, но я хочу использовать sedили awk. Это возможно?

Виджай
источник
12
если вы имеете в виду последовательные дубликаты uniq, достаточно одного.
Майкл Крелин - хакер
и в противном случае, я считаю, что это возможно awk, но это будет довольно ресурсозатратным для больших файлов.
Михаил Крелин - хакер
У дубликатов stackoverflow.com/q/24324350 и stackoverflow.com/q/11532157 есть интересные ответы, которые в идеале следует перенести сюда.
tripleee

Ответы:

290
awk '!seen[$0]++' file.txt

seen- это ассоциативный массив, в который Awk будет передавать каждую строку файла. Если строки нет в массиве, seen[$0]будет присвоено значение false. Это !логический оператор НЕ, который преобразует ложное в истинное. Awk напечатает строки, в которых выражение оценивается как истинное. В ++приросты seenтак , что seen[$0] == 1после того, как в первый раз строка найдена , а затем seen[$0] == 2, и так далее.
Awk оценивает все, кроме 0и ""(пустая строка), как истина. Если в него помещена повторяющаяся строка, seenтогда !seen[$0]будет вычислено значение false, и строка не будет записана на вывод.

Йонас Эльфстрём
источник
5
Чтобы сохранить это в файле, мы можем сделать этоawk '!seen[$0]++' merge_all.txt > output.txt
Акаш Кандпал
5
Важное предостережение: если вам нужно сделать это для нескольких файлов, и вы добавляете больше файлов в конце команды или используете подстановочный знак ... массив 'visible' заполнится повторяющимися строками из ВСЕХ файлов. Если вместо этого вы хотите обрабатывать каждый файл независимо, вам нужно сделать что-то вродеfor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9
@ NickK9, который кумулятивно устраняет дублирование нескольких файлов, само по себе потрясающе. Хороший совет
sfscs
31

Из http://sed.sourceforge.net/sed1line.txt : (Пожалуйста, не спрашивайте меня, как это работает ;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
Андре Миллер
источник
выродки ;-) +1, но расход ресурсов неизбежен.
Михаил Крелин - хакер
3
! «$ N; /^(.*)\n\1$/!P; D 'означает: «Если вы не на последней строке, прочтите другую строку. Теперь посмотрите, что у вас есть, и если это НЕ материал, за которым следует новая строка, а затем то же самое снова, распечатайте материал. Теперь удалите материал (до новой строки) ".
Бета,
2
'ГРАММ; с / \ п / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; s / \ п //; час; P 'означает, грубо говоря, «Добавить все пространство удержания к этой строке, затем, если вы видите дублированную строку, выбросите все это, в противном случае скопируйте весь беспорядок обратно в пространство удержания и распечатайте первую часть (это строка, которую вы просто читать »
Beta,
Является $!частью необходимо? Не sed 'N; /^\(.*\)\n\1$/!P; D'то же самое? Я не могу привести пример, в котором они разные на моей машине (fwiw я пробовал пустую строку в конце с обеими версиями, и они обе были в порядке).
eddi
1
Спустя почти 7 лет никто не ответил на @amichair ... <sniff> меня огорчает. ;) В любом случае, [ -~]представляет собой диапазон символов ASCII от 0x20 (пробел) до 0x7E (тильда). Они рассматриваются в печатаемые символы ASCII (связанная страница также 0x7F / удаления , но это не кажется правильным). Это делает решение неработоспособным для тех, кто не использует ASCII, или для тех, кто использует, скажем, символы табуляции. Более переносимый [^\n]включает в себя намного больше символов ... фактически все, кроме одного.
Layer B
14

Однострочник Perl, аналогичный awk-решению @jonas:

perl -ne 'print if ! $x{$_}++' file

Этот вариант удаляет завершающие пробелы перед сравнением:

perl -lne 's/\s*$//; print if ! $x{$_}++' file

Этот вариант редактирует файл на месте:

perl -i -ne 'print if ! $x{$_}++' file

Этот вариант редактирует файл на месте и делает резервную копию file.bak

perl -i.bak -ne 'print if ! $x{$_}++' file
Крис Кокнат
источник
6

Однострочник, опубликованный Андре Миллером выше, работает, за исключением последних версий sed, когда входной файл заканчивается пустой строкой и без символов. На моем Mac мой процессор просто крутится.

Бесконечный цикл, если последняя строка пуста и не содержит символов :

sed '$!N; /^\(.*\)\n\1$/!P; D'

Не зависает, но вы теряете последнюю строчку

sed '$d;N; /^\(.*\)\n\1$/!P; D'

Объяснение находится в самом конце FAQ по sed :

Сопровождающий GNU sed чувствовал, что, несмотря на проблемы с переносимостью, которые
это может вызвать, изменение команды N на печать (а не
удаление) пространства шаблонов больше соответствовало интуитивным представлениям
о том, как должна себя вести команда для «добавления следующей строки» .
Другой факт, благоприятствующий изменению, заключался в том, что "{N; command;}"
удалит последнюю строку, если в файле нечетное количество строк, но
напечатает последнюю строку, если в файле четное количество строк.

Чтобы преобразовать сценарии, в которых использовалось прежнее поведение N (удаление
пространства шаблонов при достижении EOF), в сценарии, совместимые со
всеми версиями sed, измените одиночный "N;" в "$ d; N;" ,

Брэдли Крайдер
источник
5

Альтернативный способ использования Vim (совместимый с Vi) :

Удалите повторяющиеся последовательные строки из файла:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

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

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq

Бор
источник
4

Первое решение также из http://sed.sourceforge.net/sed1line.txt

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5

основная идея:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

Объясняет:

  1. $!N;: если текущая строка НЕ ​​является последней строкой, используйте Nкоманду для чтения следующей строки pattern space.
  2. /^(.*)\n\1$/!P: если содержимое текущей строки разделено pattern spaceдвумя duplicate stringсимволами \n, что означает, что следующая строка является строкой sameс текущей строкой, мы НЕ можем распечатать ее в соответствии с нашей основной идеей; в противном случае, что означает, что текущая строка является ПОСЛЕДНИМ появлением всех повторяющихся последовательных строк, теперь мы можем использовать Pкоманду для печати символов в текущей pattern spaceутилите \n( \nтакже напечатанной).
  3. D: мы используем Dкоманду для удаления символов в текущей pattern spaceутилите \n( \nтакже удаленной), тогда содержимое pattern spaceследующей строки.
  4. и Dкоманда заставит sedперейти к своей FIRSTкоманде $!N, но НЕ будет читать следующую строку из файла или стандартного входного потока.

Второе решение легко понять (от себя):

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5

основная идея:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

Объясняет:

  1. прочтите новую строку из входного потока или файла и распечатайте ее один раз.
  2. используйте :loopкоманду set a labelnamed loop.
  3. используйте Nдля чтения следующей строки в pattern space.
  4. используйте s/^(.*)\n\1$/\1/для удаления текущей строки, если следующая строка совпадает с текущей строкой, мы используем sкоманду для выполнения deleteдействия.
  5. если sкоманда выполнена успешно, то используйте tloopкоманду force, sedчтобы перейти к labelназванному loop, что сделает тот же цикл для следующих строк, при этом не будет повторяющихся последовательных строк строки, которая есть latest printed; в противном случае используйте Dкоманду для deleteстроки, которая совпадает с latest-printed line, и принудительный sedпереход к первой команде, которая является pкомандой, содержимое текущей pattern spaceявляется следующей новой строкой.
Weike
источник
Та же команда на Windows , с BusyBox:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
поглотитель
-1

Этого можно добиться с помощью awk.
Ниже линии будут отображаться уникальные значения.

awk file_name | uniq

Вы можете вывести эти уникальные значения в новый файл

awk file_name | uniq > uniq_file_name

новый файл uniq_file_name будет содержать только уникальные значения, без дубликатов

Aashutosh
источник
-4
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

Удаляет повторяющиеся строки с помощью awk.

Sadhun
источник
1
Это нарушит порядок строк.
Vijay
1
Что такое текстовый файл размером 20 ГБ? Слишком медленно.
Александр
Как всегда, бесполезно. Во всяком случае, это уже делается само по себе и не требует ввода ровно одного слова в строке. catuniq
tripleee