Tengo una enorme cantidad de datos: entre 30 y 40 millones de elementos de datos. Necesitamos procesar estos archivos y enviarlos a otro equipo de interfaz.
A continuación se muestra el formato de mi archivo que recibimos.
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
Necesito imprimir en mi archivo de salida todas las columnas. Dado que esto está relacionado con el uso de GPRS, debemos agrupar porc1-c4y luego, si todo coincide, debemos sumar losc5e imprima todo en el archivo de salida.
A continuación se muestra un archivo de salida de muestra.
c1 c2 c3 c4 c5 c6
A B C D 9 s
A B E F 13 s
C D E F 9 s
También escuché que este flujo de trabajo funciona mucho más rápido en Perl que en los scripts de Unix.
Respuesta1
Otra perl
solución, similar a la respuesta de @terdon pero con mejor formato de salida:
$ 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
Respuesta2
En cuanto a la elección de herramientas: normalmente, cuanto más especializada es una herramienta, más rápida es. Entonces, las tuberías que involucran tr
, cut
, grep
, sort
, etc. tienden a ser más rápidas que sed
las que tienden a ser más rápidas que awk
las que tienden a ser más rápidas que perl
, python
, ruby
. Pero eso, por supuesto, también depende mucho de la tarea. Si lee que Perl es más rápido, entonces o leyó mal o la comparación fue con un bucle de shell que procesa una línea a la vez (eso definitivamente será lento para archivos con millones de líneas).
Si su entrada está en un formulario en el que las líneas a fusionar son consecutivas, awk es una buena apuesta (no existe una forma sensata de realizar adiciones en 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
'
Si las líneas no son consecutivas, puedes hacerlo ordenándolas. Las implementaciones típicas sort
están altamente optimizadas y son más rápidas que la manipulación de estructuras de datos en lenguajes de alto nivel.
sort | awk …
Esto supone que los delimitadores de sus columnas son consistentes, por ejemplo, siempre una pestaña. Si no es así, preprocese la entrada para que así sea o utilícela sort -k1,1 -k2,2 -k3,3 -k4,4
para comparar estos campos específicos sin tener en cuenta los delimitadores.
Respuesta3
Esto podría ayudarte a comenzar:
perl -ane '$h{"@F[0 .. 3]"} += $F[4] }{ print "$_ $h{$_}\n" for keys %h' input-file
No imprime la última columna porque no especificó qué hacer con ella. Además, no maneja correctamente la línea del encabezado, pero debería ser fácil de solucionar.
Respuesta4
Si te entiendo correctamente, quieres algo como esto:
$ 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
Esto no mantendrá sus columnas alineadas. No sé si esto es un problema para usted. Sin embargo, tratará el encabezado correctamente y debería producir el resultado que necesita.
Explicación
perl -lane
:-l
Elimina nuevas líneas del final de cada cadena y las agrega a cadaprint
declaración. Dividea
cada línea de entrada en campos en espacios en blanco y guarda los campos en la matriz@F
. Losn
significadoslea el archivo de entrada línea por línea y aplique el script proporcionado por-e
.
Aquí está la misma frase en forma de guión comentado:
#!/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"
}