Somme en bloc avec AWK (redémarrer la sum lorsque le motif change)

J'ai un file comme celui-ci:

A 100 A 200 A 300 #sum=600 B 400 B 500 #sum=900 A 600 A 700 A 800 #sum=2100 

J'aimerais que la sortie soit:

 A 600 B 900 A 2100 C sum_of_C D sum_of_D 

Je peux le faire avec for , sed , grep et awk .

Cependant, comme j'apprends awk , j'aimerais écrire un script awk . Jusqu'à présent, j'ai:

 if (${NR {print $1}} == ${NR-1 {print $1}}) sum+=$2 print $0"\t"sum else sum=$2 print $0"\t"sum 

awk -f awkscript file n'a pas abouti. Quelle est la solution?

Je ne suis pas complètement sûr de ce que vous essayez d'y faire. NR est le nombre d'loggings; utilisez NF pour le nombre de champs, si c'est ce que vous visez. Vous ne pouvez pas mettre {} blocs au milieu de choses comme ça.

Je pense que ce que vous visez est de comparer la valeur d'un champ de cette ligne avec un champ de la ligne précédente, en imprimant la sum lorsque nous atteignons un nouveau «groupe» de données. Si c'est le cas, ce script fera ce que vous voulez et je pense que cela correspond à peu près à ce que vous visiez:

 { if (last && $1 != last) { print last, sum sum = 0 } sum = sum + $2 last = $1 } END { print last, sum } 

Nous faisons une last variable pour tenir la valeur du premier champ ( $1 ) sur la ligne précédente. Nous l'utiliserons pour déterminer quel groupe nous examinons.

  • Pour chaque ligne (parce que nous avons { ... } au niveau supérieur), nous vérifions d'abord si a) last est défini (parce que nous ne voulons rien imprimer sur la première ligne) et b) la valeur de le premier champ est différent de la last . Si c'est le cas, nous imprimons la valeur de last , un espace (à cause de , ) et la sum nous avons calculée. (Si vous voulez un onglet, utilisez "\t" entre guillemets comme vous aviez)
  • Après l'printing, nous réinitialisons la sum à zéro.
  • De toute façon, nous ajoutons la valeur du deuxième champ ( $2 ) à la sum .
  • Pour chaque ligne, nous sauvegardons le premier champ (notre groupe) en last afin que nous puissions l'utiliser pour la comparaison sur la ligne suivante.
  • Enfin, nous voulons également imprimer le dernier groupe. Pour cela, nous utilisons un bloc END { ... } . Il fonctionne à la fin du programme lorsque nous manquons de données. Nous imprimons la sum et le groupe avec lequel nous travaillons comme nous l'avons fait auparavant.

Si je cours:

 awk -f sum.awk < data 

avec votre file de données, j'obtiens cette sortie:

 A 600 B 900 A 2100 

comme voulu.


Il existe des façons plus simples de le faire, à la fois dans awk et autrement. En particulier, nous pouvons replace le corps ci-dessus par:

 last && $1 != last { print last, sum sum = 0 } { sum = sum + $2 last = $1 } 

Nous utilisons ici la syntaxe des blocs conditionnels de awk plutôt qu'un test explicite if : le comportement de ce programme est identique à celui ci-dessus, mais il est plus idiomatique. Ce n'est pas très différent dans cet exemple, mais il est utile de savoir si vous apprenez awk.


Si l'exemple de file que vous avez donné est littéralement ce qu'il est, avec #sum= lignes (ou similaire), vous pouvez utiliser ce script:

 { sum = sum + $2 if (NF == 3) { print $1, sum sum = 0 } } 

Pour chaque ligne, ceci ajoute la valeur du second champ à la variable sum . Sur les lignes qui ont exactement trois champs ( NF == 3 ), nous imprimons notre total et réinitialisons la sum à zéro.

Si votre file est suffisamment petit pour que toutes les sums puissent tenir en memory, vous pouvez faire quelque chose aussi simple que:

 $ awk '{sum[$1]+=$2}END{for(pat in sum){print pat,sum[pat]}}' file A 2700 B 900 

Voici la même chose qu'un script awk commenté:

 #!/usr/bin/awk -f { ## Here, we use $1 as the key of an associative array ## and increment its current value by $2. The result of ## this will be an array element for each different $1 in ## the file whose value will be the sum of all associated $2s. sum[$1]+=$2 } ## The END{} block is exacuted after the entire file ## has been processed. END{ ## Iterate through the keys of the array (the $1s), ## saving each as 'pat'. Then, print the current value of ## 'pat' as well as the associated value (the sum) from ## the array. for(pat in sum){ print pat,sum[pat] } } 

Le seul problème possible avec cette approche est que si vous avez autant de lignes que le fait de conserver le tableau de $1 vous fera manquer de memory. Ce n'est pas très probable sur un système moderne. D'autre part, cette approche fonctionne également lorsque les lignes de votre file ne sont pas en ordre car il peut traiter des files non sortingés.