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

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

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

8 ответов

Решение
awk '!seen[$0]++' file.txt

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

С 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'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Первое решение также от 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 Util \n (\n также напечатано).
  3. D: мы используем D команда для удаления символов в текущем pattern space Util \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 Команда установить label названный loop,
  3. использование N читать следующую строку в pattern space,
  4. использование s/^(.*)\n\1$/\1/ чтобы удалить текущую строку, если следующая строка совпадает с текущей строкой, мы используем s Команда сделать delete действие.
  5. если s команда выполнена успешно, затем используйте tloop командование sed прыгать к label названный loop, который будет делать тот же цикл для следующих строк, если нет повторяющихся последовательных строк строки, которая latest printed; в противном случае используйте D командовать delete линия, которая совпадает сlatest-printed lineи сила sed перейти к первой команде, которая является p команда, содержание текущего pattern space это следующая новая строка.

Uniq можно было бы обмануть конечными пробелами и табуляциями. Чтобы подражать тому, как человек производит сравнение, я обрезаю все конечные пробелы и табуляции перед сравнением.

Я думаю, что $!N; нужны фигурные скобки, иначе он продолжается, и это является причиной бесконечного цикла.

У меня есть bash 5.0 и sed 4.7 в Ubuntu 20.10. Вторая однострочная строка не работает при совпадении набора символов.

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

пастебин

# First line in a set of duplicate lines is kept, rest are deleted.
# Emulate human eyes on trailing spaces and tabs by trimming those.
# Use after norepeat() to dedupe blank lines.

dedupe() {
 sed -E '
  $!{
   N;
   s/[ \t]+$//;
   /^(.*)\n\1$/!P;
   D;
  }
 ';
}

# Delete duplicate, nonconsecutive lines from a file. Ignore blank
# lines. Trailing spaces and tabs are trimmed to humanize comparisons
# squeeze blank lines to one

norepeat() {
 sed -n -E '
  s/[ \t]+$//;
  G;
  /^(\n){2,}/d;
  /^([^\n]+).*\n\1(\n|$)/d;
  h;
  P;
  ';
}

lastrepeat() {
 sed -n -E '
  s/[ \t]+$//;
  /^$/{
   H;
   d;
  };
  G;
  # delete previous repeated line if found
  s/^([^\n]+)(.*)(\n\1(\n.*|$))/\1\2\4/;
  # after searching for previous repeat, move tested last line to end
  s/^([^\n]+)(\n)(.*)/\3\2\1/;
  $!{
   h;
   d;
  };
  # squeeze blank lines to one
  s/(\n){3,}/\n\n/g;
  s/^\n//;
  p;
 ';
}

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

awk file_name | uniq

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

awk file_name | uniq > uniq_file_name

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

cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

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

Другие вопросы по тегам