У меня огромная куча данных — около 30–40 миллионов элементов данных. Нам нужно обработать эти файлы и отправить другой команде интерфейса.
Ниже представлен мой формат файла, который мы получаем
c1 c2 c3 c4 c5 c6
A B C D 5 s
A B C D 4 s
A B E F 5 s
A B E F 8 S
C D E F 9 S
Мне нужно распечатать в моем выходном файле все столбцы. Поскольку это связано с использованием GPRS, нам нужно сгруппировать пос1 - с4и затем, если все совпадает, нам нужно суммироватьс5и распечатать все в выходном файле.
Ниже приведен пример выходного файла.
c1 c2 c3 c4 c5 c6
A B C D 9 s
A B E F 13 s
C D E F 9 s
Я также слышал, что этот рабочий процесс выполняется намного быстрее в Perl, чем в скриптах Unix.
решение1
Другое perl
решение, похожее на ответ @terdon, но с лучшим форматом вывода:
$ perl -alne '
(print && next) if $. == 1;
$h{"@F[0..3]"}{s} += $F[4];
$h{"@F[0..3]"}{t} = $F[5];
END {
for (keys %h) {
printf "%-4s%-4s%-4s%-4s%-4s%-4s",split(" ",$_),$h{$_}{s},$h{$_}{t};
printf "\n";
}
}' file
c1 c2 c3 c4 c5 c6
A B E F 13 S
A B C D 9 s
C D E F 9 S
решение2
Что касается выбора инструментов: обычно, чем более специализирован инструмент, тем он быстрее. Так что каналы, включающие tr
, cut
, grep
, , и т. д. , как правило, быстрее, чем sort
, , , . Но это, конечно, во многом зависит и от задачи. Если вы читаете, что Perl быстрее, то либо вы неправильно поняли, либо сравнение было с циклом оболочки, который обрабатывает одну строку за раз (это определенно будет медленно для файлов с миллионами строк).sed
awk
perl
python
ruby
Если ваши входные данные представлены в форме, где строки для объединения являются последовательными, то awk — хороший выбор (в sed нет разумного способа выполнять сложения).
awk -v OFS='\t' ' # use tabs to separate output fields
NR==1 {print; next} # keep the first line intact
function flush () { # function to print a completed sum
if (key != "") print previous, sum, more;
sum=0
}
{key = $1 OFS $2 OFS $3 OFS $4} # break out the comparison key
key!=previous {flush()} # if the comparison key has changed, print the accumulated sum
{previous=key; sum+=$5; more=$6} # save the current line
END {flush()} # print the last
'
Если строки не последовательны, вы можете сделать их таковыми с помощью сортировки. Типичные sort
реализации высоко оптимизированы и быстрее, чем манипулирование структурами данных в языках высокого уровня.
sort | awk …
Это предполагает, что ваши разделители столбцов согласованы, например, всегда табуляция. Если это не так, либо предварительно обработайте ввод, чтобы сделать их таковыми, либо используйте sort -k1,1 -k2,2 -k3,3 -k4,4
для сравнения этих конкретных полей, не принимая во внимание разделители.
решение3
Это может вам помочь начать:
perl -ane '$h{"@F[0 .. 3]"} += $F[4] }{ print "$_ $h{$_}\n" for keys %h' input-file
Он не печатает последний столбец, так как вы не указали, что с ним делать. Кроме того, он не обрабатывает строку заголовка правильно, но это должно быть легко исправить.
решение4
Если я правильно вас понял, вы хотите что-то вроде этого:
$ perl -lane 'if($.>1){$k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5]}
else{print "@F"}
END{
foreach (keys(%k)){ print "$_ $k{$_}{sum} $k{$_}{last}"}
}' file
c1 c2 c3 c4 c5 c6
C D E F 9 S
A B E F 13 S
A B C D 9 s
Это не сохранит выравнивание ваших столбцов, я не знаю, является ли это проблемой для вас. Однако это будет правильно обрабатывать заголовок и должно выдавать нужный вам вывод.
Объяснение
perl -lane
:-l
удаляет символы новой строки из конца каждой строки и добавляет их к каждомуprint
оператору.a
разбивает каждую входную строку на поля по пробелам и сохраняет поля в массиве@F
.n
Средствапрочитайте входной файл построчно и примените скрипт, заданный-e
.
Вот тот же однострочный текст в форме прокомментированного скрипта:
#!/usr/bin/env perl
## This is the equivalent of perl -ne
## in the one-liner. It iterates through
## the input file.
while (<>) {
## This is what the -a flag does
my @F=split(/\s+/);
## $. is the current line number.
## This simply tests whether we are on the
## first line or not.
if ($.>1) {
## @F[0..3] is an array slice. It holds fields 1 through 4.
## The slice is used as a key for the hash %k and the 5th
## field is summed to $k{slice}{sum} while the last column is
## saved as $k{slice}{last}.
$k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5];
}
## If this is the first line, print the fields.
## I am using print "@F" instead of a simple print
## so that all lines are formatted in the same way.
else {
print "@F\n";
}
}
## This is the same as the END{} block
## in the one liner. It will be run after
## the whole file has been read.
## For each of the keys of the hash %k
foreach (keys(%k)){
## Print the key ($_, a special variable in Perl),
## the value of $k{$key}{sum} (the summed values),
## and the last column.
print "$_ $k{$_}{sum} $k{$_}{last}\n"
}