膨大な量のデータがあります。データ項目は 3,000 万から 4,000 万個ほどです。これらのファイルを処理して、別のインターフェース チームに送信する必要があります。
以下は私たちが受け取るファイル形式です
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
また、このワークフローは Unix スクリプトよりも Perl の方がはるかに高速に動作するとも聞きました。
答え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
になる傾向があり、は、よりも高速になる傾向があり、は、、よりも高速になる傾向があります。ただし、もちろん、これはタスクにも大きく依存します。Perl の方が高速であると読んだ場合は、読み間違えたか、一度に 1 行ずつ処理するシェル ループと比較したかのどちらかです (数百万行のファイルでは間違いなく遅くなります)。cut
grep
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
入力ファイルを1行ずつ読み込み、指定されたスクリプトを適用する。-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"
}