2012-04-23 17 views
26

Ho un file di grandi dimensioni che contiene i dati in questo modo:GROUP BY/SUM da shell

a 23 
b 8 
a 22 
b 1 

voglio essere in grado di ottenere questo:

a 45 
b 9 

posso prima ordinare questo file e poi fallo in Python eseguendo una scansione del file una volta. Che cosa è un buon modo diretto da linea di comando per farlo?

risposta

26
awk '{ 
    arr[$1]+=$2 
    } 
    END { 
    for (key in arr) printf("%s\t%s\n", key, arr[key]) 
    }' file \ 
    | sort +0n -1 

Spero che questo aiuti.

+0

cosa esattamente fanno tali argomenti fanno per ordinare? Non li vedo nella pagina man e la pagina di invocazione mi ha lasciato confuso. – EricR

+1

Le versioni moderne di ordinamento preferiscono la sintassi '-k' per specificare le chiavi di ordinamento:' ordina -nk1,1' invece di 'ordina + 0n -1'. Ma dal momento che le chiavi sono lettere, perché stai specificando '-n' comunque? –

+0

@EricR: '+ 0n -1' è antiquato per' -n -k1,1': ordina numericamente il primo campo (separato da spazi bianchi). –

8

Non c'è bisogno di awk qui, o anche specie - se si dispone di Bash 4.0, è possibile utilizzare gli array associativi:

#!/bin/bash 
declare -A values 
while read key value; do 
    values["$key"]=$(($value + ${values[$key]:-0})) 
done 
for key in "${!values[@]}"; do 
    printf "%s %s\n" "$key" "${values[$key]}" 
done 

... o, se si ordina prima il file (che saranno più efficiente in termini di memoria, GNU sort è in grado di eseguire trucchi per ordinare file più grandi della memoria, che in genere uno script ingenuo - sia in awk, python o shell - non è possibile farlo in un modo che funzioni in le versioni più vecchie (mi aspetto che la segue per lavorare attraverso bash 2.0):

#!/bin/bash 
read cur_key cur_value 
while read key value; do 
    if [[ $key = "$cur_key" ]] ; then 
    cur_value=$((cur_value + value)) 
    else 
    printf "%s %s\n" "$cur_key" "$cur_value" 
    cur_key="$key" 
    cur_value="$value" 
    fi 
done 
printf "%s %s\n" "$cur_key" "$cur_value" 
+3

Heck, con un minimo di munging come sopra funzionerebbe in vanilla Bourne, non bash richiesto. While read key value; do if ["$ key" = "$ cur_key"]; then cur_value = \ 'expr $ cur_value + $ valore \'; else echo "$ cur_key $ cur_value"; cur_key = "$ chiave"; cur_value = "$ valore"; fi; fatto; echo "$ cur_key $ cur_value" ' –

+2

@MarkReed, sicuramente così, anche se l'impatto delle prestazioni della subshell che esegue' expr' è sufficiente che l'espansione di POSIX sh '$ (())' sarebbe migliore; mentre '(())' è un'estensione bash, '$ (())' è conforme allo standard; è solo il pre-1991-POSIX-standard Bourne sh dove è richiesto 'expr'. –

2

Un modo utilizzando perl:

perl -ane ' 
    next unless @F == 2; 
    $h{ $F[0] } += $F[1]; 
    END { 
     printf qq[%s %d\n], $_, $h{ $_ } for sort keys %h; 
    } 
' infile 

Contenuto infile:

a 23 
b 8 
a 22 
b 1 

uscita:

a 45 
b 9 
2

Con GNU awk (versioni meno di 4):

WHINY_USERS= awk 'END { 
    for (E in a) 
    print E, a[E] 
    } 
{ a[$1] += $2 }' infile 

Con GNU awk> = 4:

awk 'END { 
    PROCINFO["sorted_in"] = "@ind_str_asc" 
    for (E in a) 
    print E, a[E] 
    } 
{ a[$1] += $2 }' infile 
+0

Mi sembra di aver inciampato in questo quasi tre anni di ritardo. Che cosa fa esattamente la variabile 'WHINY_USERS'? –

+1

Ordina le chiavi dell'array in [ordine ASCII] (http://awk.info/?doc/tip/whinyUsers.html). –

+2

Link è morto, questo è più probabile che viva: https://stackoverflow.com/q/11697556/476716 – OrangeDog

6

Questo Perl one-liner sembra fare il lavoro:

perl -nle '($k, $v) = split; $s{$k} += $v; END {$, = " "; foreach $k (sort keys %s) {print $k, $s{$k}}}' inputfile 
0

Ciò può essere ottenuto facilmente con la seguente singolo-liner:

cat /path/to/file | termsql "SELECT col0, SUM(col1) FROM tbl GROUP BY col0" 

Or.

termsql -i /path/to/file "SELECT col0, SUM(col1) FROM tbl GROUP BY col0" 

Qui un pacchetto Python, termsql, viene utilizzato, che è un involucro intorno SQLite. Si noti, che attualmente non è caricare a PyPI, e anche può essere installato solo a livello di sistema (setup.py è un po 'rotto), come:

sudo pip install https://github.com/tobimensch/termsql/archive/master.zip