Как я могу удалить повторяющиеся строки в файле в 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.
Объясняет:
$!N;
: если текущая строка НЕ является последней строкой, используйтеN
Команда, чтобы прочитать следующую строку вpattern space
,/^(.*)\n\1$/!P
: если содержание текущегоpattern space
это дваduplicate string
разделены по\n
, что означает, что следующая строкаsame
с текущей строкой мы НЕ можем печатать ее в соответствии с нашей основной идеей; в противном случае, что означает, что текущая строка является ПОСЛЕДНИМ видом всех ее повторяющихся последовательных строк, теперь мы можем использоватьP
Команда для печати символов в текущемpattern space
Util\n
(\n
также напечатано).D
: мы используемD
команда для удаления символов в текущемpattern space
Util\n
(\n
также удаляется), то содержаниеpattern space
это следующая строка.- а также
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.
Объясняет:
- прочитать новую строку из входного потока или файла и распечатать его один раз.
- использование
:loop
Команда установитьlabel
названныйloop
, - использование
N
читать следующую строку вpattern space
, - использование
s/^(.*)\n\1$/\1/
чтобы удалить текущую строку, если следующая строка совпадает с текущей строкой, мы используемs
Команда сделатьdelete
действие. - если
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.