我有大量數據——大約 3000 到 4000 萬個數據項。我們需要處理這些文件並發送給另一個介面團隊。
以下是我們收到的文件格式
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 使用有關,因此我們需要分組c1 - c4然後如果一切都匹配,我們需要總結c5,並列印輸出文件中的所有內容。
下面是一個範例輸出檔。
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
管道往往比, ,更快。但這當然也很大程度取決於任務。如果您讀到 Perl 更快,那麼您可能誤讀了,或者比較是針對一次處理一行的 shell 循環(對於具有數百萬行的文件來說,這肯定會很慢)。sort
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"
}