web-dev-qa-db-fra.com

GROUP BY/SUM du shell

J'ai un gros fichier contenant des données comme celle-ci:

a 23
b 8
a 22
b 1

Je veux pouvoir obtenir ceci:

a 45
b 9

Je peux d’abord trier ce fichier puis le faire en Python en l’analysant une fois. Quel est un bon moyen direct en ligne de commande de faire cela?

30
Legend

Edit: La solution moderne (GNU/Linux), comme mentionné dans les commentaires il y a quelques années ;-).

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

La solution initialement publiée, basée sur les anciennes options Unix sort:

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

J'espère que ça aide.

31
shellter

Ce one-liner Perl semble faire le travail:

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

Pas besoin de awk ici, ni même de trier - si vous avez Bash 4.0, vous pouvez utiliser des tableaux associatifs:

#!/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

... ou si vous triez d'abord le fichier (ce qui sera plus efficace en termes de mémoire; GNU sort est capable d'effectuer des astuces pour trier des fichiers plus volumineux que la mémoire, ce qui est un script naïf python ou Shell - ce n’est généralement pas le cas), vous pouvez le faire d’une manière qui fonctionnera dans les anciennes versions (les versions suivantes devraient fonctionner jusqu’à 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"
8
Charles Duffy

Une manière en utilisant Perl:

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

Contenu de infile:

a 23
b 8
a 22
b 1

Sortie:

a 45
b 9
2
Birei

Avec GNU awk (versions inférieures à 4):

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

Avec GNU awk > = 4:

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

Ceci peut être facilement réalisé avec le liner unique suivant:

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

Ou.

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

Ici, un paquet Python, termsql , est utilisé, qui encapsule SQLite. Notez qu’à l’heure actuelle, il n’est pas transféré vers PyPI , et ne peut également être installé que sur l’ensemble du système (setup.py est un peu endommagé), comme:

Sudo pip install https://github.com/tobimensch/termsql/archive/master.Zip
0
saaj