Ich habe einen riesigen Datenberg – etwa 30 bis 40 Millionen Datenelemente. Wir müssen diese Dateien verarbeiten und an ein anderes Schnittstellenteam senden.
Unten ist mein Dateiformat, das wir erhalten
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
Ich muss in meiner Ausgabedatei alle Spalten drucken. Da dies mit der GPRS-Nutzung zusammenhängt, müssen wir nachc1 – c4und wenn dann alles passt, müssen wir diec5, und drucken Sie alles in der Ausgabedatei.
Unten sehen Sie ein Beispiel für eine Ausgabedatei.
c1 c2 c3 c4 c5 c6
A B C D 9 s
A B E F 13 s
C D E F 9 s
Ich habe auch gehört, dass dieser Arbeitsablauf in Perl viel schneller funktioniert als mit Unix-Skripten.
Antwort1
Eine andere perl
Lösung, ähnlich der Antwort von @terdon, aber mit besserer Formatausgabe:
$ 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
Antwort2
Zur Auswahl der Tools: Normalerweise ist ein Tool umso schneller, je spezialisierter es ist. Pipes mit tr
, cut
, grep
, sort
, usw. sind also tendenziell schneller als sed
welche tendenziell schneller sind als awk
welche tendenziell schneller sind als perl
, python
, ruby
. Aber das hängt natürlich auch stark von der Aufgabe ab. Wenn Sie lesen, dass Perl schneller ist, dann haben Sie entweder etwas falsch gelesen oder der Vergleich bezog sich auf eine Shell-Schleife, die jeweils eine Zeile verarbeitet (was bei Dateien mit Millionen von Zeilen definitiv langsam sein wird).
Wenn Ihre Eingabe in einem Format erfolgt, bei dem die zusammenzuführenden Zeilen aufeinander folgen, ist awk eine gute Wahl (es gibt keine vernünftige Möglichkeit, Additionen in sed durchzuführen).
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
'
Wenn die Zeilen nicht aufeinander folgen, können Sie sie durch Sortieren aufeinander folgen lassen. Typische sort
Implementierungen sind hoch optimiert und schneller als die Manipulation von Datenstrukturen in höheren Programmiersprachen.
sort | awk …
Dies setzt voraus, dass Ihre Spaltentrennzeichen einheitlich sind, z. B. immer ein Tabulator. Wenn dies nicht der Fall ist, verarbeiten Sie die Eingabe entweder vorab, um sie einheitlich zu machen, oder verwenden Sie sort -k1,1 -k2,2 -k3,3 -k4,4
zum Vergleichen dieser spezifischen Felder, ohne die Trennzeichen zu berücksichtigen.
Antwort3
Dies könnte Ihnen den Einstieg erleichtern:
perl -ane '$h{"@F[0 .. 3]"} += $F[4] }{ print "$_ $h{$_}\n" for keys %h' input-file
Die letzte Spalte wird nicht gedruckt, da Sie nicht angegeben haben, was damit geschehen soll. Außerdem wird die Kopfzeile nicht richtig verarbeitet, aber das sollte sich leicht beheben lassen.
Antwort4
Wenn ich Sie richtig verstehe, möchten Sie so etwas:
$ 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
Dadurch bleiben Ihre Spalten nicht ausgerichtet. Ich weiß nicht, ob das für Sie ein Problem darstellt. Die Kopfzeile wird jedoch korrekt verarbeitet und sollte die gewünschte Ausgabe erzeugen.
Erläuterung
perl -lane
: Das-l
entfernt Zeilenumbrüche am Ende jedes Strings und fügt sie jederprint
Anweisung hinzu. Dasa
teilt jede Eingabezeile in Felder mit Leerzeichen auf und speichert die Felder im Array@F
. Dasn
bedeutetLesen Sie die Eingabedatei Zeile für Zeile und wenden Sie das von-e
.
Hier ist derselbe Einzeiler in kommentierter Skriptform:
#!/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"
}