Я был бы склонен использовать awk
для этого.
script.awk
{ x = count[$1,$2,$3,$4]++; line[$1,$2,$3,$4,x] = $0 }
END { for (key in count)
{
kc = count[key]
if (kc > 1)
{
for (i = 0; i < kc; i++)
{
print line[key,i]
}
}
}
}
Для каждой строки увеличивайте количество строк с первыми четырьмя значениями поля в качестве ключа. Сохраните текущую строку в правильной последовательности. В конце для каждого ключа, количество которых больше единицы, выведите каждую из сохраненных строк для этого ключа.
Пробный запуск
$ awk -f script.awk data
jax 5 add1 1234 City5
jax 5 add1 1234 City6
eric 5 add1 1234 City1
eric 5 add1 1234 City3
eric 5 add1 1234 City4
$
Обратите внимание, что при этом ключи генерируются в другом порядке, в котором они появляются в файле (первая запись eric, 5, add1, 1234
предшествует первой записи jax, 5, add1, 1234
).
Это можно было бы решить, если это необходимо сделать.
script2.awk
{ x = count[$1,$2,$3,$4]++;
line[$1,$2,$3,$4,x] = $0
if (x == 0)
seq[n++] = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4
}
END { for (s = 0; s < n; s++)
{
key = seq[s]
kc = count[key]
if (kc > 1)
{
for (i = 0; i < kc; i++)
{
print line[key,i]
}
}
}
}
SUBSEP
— это символ, используемый для разделения компонентов многоэлементного ключа, поэтому присваивание seq[n++]
записывает значение, используемое в качестве индекса в count[$1,$2,$3,$4]
. Массив seq
записывает каждый ключ (первые четыре столбца) в том порядке, в котором они появляются. Последовательный проход по этому массиву дает ключи в том порядке, в котором появляется первая запись.
Пробный запуск
$ awk -f script2.awk data
eric 5 add1 1234 City1
eric 5 add1 1234 City3
eric 5 add1 1234 City4
jax 5 add1 1234 City5
jax 5 add1 1234 City6
$
Предварительная обработка данных для экономии памяти и ускорения обработки
Приведенный выше код хранит много данных в памяти. Он имеет полную копию каждой строки в файлах данных; у него есть ключ с первыми четырьмя полями; у него есть еще один ключ с четырьмя полями плюс целое число. Для большинства практических целей это 3 копии данных. Если файлы даты большие, это может быть проблемой. Однако, учитывая, что в выборке данных строка jerry
появляется в середине строк eric
, невозможно добиться большего успеха, если только данные не отсортированы в первую очередь. Тогда вы знаете, что все связанные строки находятся в файле вместе, и вы можете обрабатывать его намного проще.
script3.awk
{
new_key = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4
if (new_key == old_key)
{
if (old_line != "") { print old_line; old_line = "" }
print $0
}
else
{
old_line = $0
old_key = new_key
}
}
Пробный запуск
$ sort data | awk -f script3.awk
eric 5 add1 1234 City1
eric 5 add1 1234 City3
eric 5 add1 1234 City4
jax 5 add1 1234 City5
jax 5 add1 1234 City6
$
Конечно, это совпадение, что eric
предшествует jax
в алфавитной последовательности; при сортировке вы теряете исходную последовательность данных. Но скрипт script3.awk
хранит в памяти максимум два ключа и одну строку, что не будет нагружать память. Добавление времени сортировки по-прежнему, вероятно, дает вам измеримую экономию по сравнению с исходным механизмом обработки.
Если первоначальный заказ критичен, вам придется проделать гораздо больше работы. Я думаю, что это включает в себя нумерацию каждой строки в исходном файле, сортировку с использованием номера строки в качестве пятого ключа после того, как первые четыре ключа группируют одни и те же ключи вместе, а затем определяют каждую группу строк с теми же четырьмя значениями ключа с тем же номером строки , затем снова отсортируйте по номеру группы и порядковому номеру внутри группы и передайте это модифицированной версии обработки в сценарии script3.awk
. Но это все же может быть лучше, чем оригинал, если файлы находятся в диапазоне гигабайт. Однако единственный способ убедиться в этом — провести измерения на примерах реалистичных размеров.
Например:
nl data |
sort -k2,2 -k3,3 -k4,4 -k5,5 -k1,1n |
awk '{ new_key = $2 SUBSEP $3 sUBSEP $4 SUBSEP $5
if (old_key != new_key) { grp_seq = $1 }
print grp_seq, $0
old_key = new_key
}' |
sort -k1,1n -k2,2n
Это сильно порождает:
1 1 name age address phone city
2 2 eric 5 add1 1234 City1
2 4 eric 5 add1 1234 City3
2 5 eric 5 add1 1234 City4
3 3 jerry 5 add1 1234 City2
6 6 jax 5 add1 1234 City5
6 7 jax 5 add1 1234 City6
8 8 niko 5 add1 1234 City7
Затем вы можете применить модифицированную версию script3.awk
, которая игнорирует $1
и $2
для получения желаемого результата. Или вы можете запустить показанный вывод через программу, которая удаляет два ведущих столбца.
person
Jonathan Leffler
schedule
15.10.2015