Comment additionner rapidement tous les nombres d'un fichier?


16

Chaque ligne contient du texte et des nombres dans une colonne. J'ai besoin de calculer la somme des nombres dans chaque ligne. Comment puis je faire ça? THX

example.log contient:

time=31sec
time=192sec
time=18sec
time=543sec

La réponse devrait être 784


J'ai essayé cette méthode awk '{sum + = $ 1}; END {print sum} 'example.log mais c'est uniquement pour les nombres en ligne
Jack

2
Il y a presque la même question dans Stack Overflow : comment puis-je additionner rapidement tous les nombres dans un fichier? . Peut-être le temps d'avoir des doublons intersites?
fedorqui

Réponses:


18

Si votre option de grepsupport -o, vous pouvez essayer:

$ grep -o '[[:digit:]]*' file | paste -sd+ - | bc
784

POSIX:

$ printf %d\\n "$(( $(tr -cs 0-9 '[\n*]' <file | paste -sd+ -) ))"
784

16

Avec une version plus récente (4.x) de GNU awk:

awk 'BEGIN {FPAT="[0-9]+"}{s+=$1}END{print s}'

Avec d'autres, awkessayez:

awk -F '[a-z=]*' '{s+=$2}END{print s}'

4
Vous avez besoin s+0dans le cas où sest vide, il s'imprimera 0au lieu de vide.
cuonglm

Permettez-moi d'expliquer cela. - Il n'y a qu'un seul cas où speut être vide; si les données d'entrée ne contiennent pas de lignes (c'est-à-dire s'il n'y a pas d'entrée du tout ). Dans ce cas, deux comportements sont possibles; 1) pas d'entrée => pas de sortie, ou 2) toujours sortir quelque chose, si seulement 0. Les deux sont des options sensées selon le contexte de l'application. L' +0adresse concerne l'option 2). Pour répondre à l'option 1), vous devriez plutôt écrire END {if(s) print s}. - Par conséquent, cela n'a aucun sens de supposer l'une ou l'autre option (pour ce cas de coin sans données) jusqu'à ce qu'elle soit spécifiée par la question.
Janis

10
awk -F= '{sum+=$2};END{print sum}'

2
Nous préférons les réponses longues. Pouvez-vous expliquer comment cela fonctionne?
slm

2
@slm, cette réponse n'est pas plus ou moins verbeuse que les autres réponses ici et s'explique d'elle-même. Il a également l'avantage de travailler avec des entrées commetime=1.4e5sec
Stéphane Chazelas

@ StéphaneChazelas - d'accord, mais il s'agit d'un nouvel utilisateur et nous encourageons les utilisateurs à fournir plus que des réponses sur une seule ligne. Un peu de texte expliquant comment cela fonctionne en ferait une réponse beaucoup plus forte que le simple code.
slm

4
@slm, c'est un nouvel utilisateur avec l'une des meilleures réponses (d'un point de vue technique) et il obtient deux downvotes et un commentaire négatif. Pas un accueil très chaleureux.
Stéphane Chazelas

1
@TomFenech, la syntaxe POSIX pour awk nécessite que ces éléments de modèle / action soient séparés par ";" ou "newline", vous pouvez donc trouver des implémentations awk où il échoue sans cela ";".
Stéphane Chazelas

7

Un autre GNU awk:

awk -v RS='[0-9]+' '{n+=RT};END{print n}'

Un perl:

perl -lne'$n+=$_ for/\d+/g}{print$n'

UN POSIX:

tr -cs 0-9 '[\n*]' | grep . | paste -sd + - | bc

6
sed 's/=/ /' file | awk '{ sum+=$2 } END { print sum}'

Réponse sedawk --field-separator = '{ sum+=$2 } END { print sum}' data.dat
géniale

@ user1717828: vous devriez plutôt utiliser le (plus court et plus compatible!) -F'='au lieu de--field-separator =
Olivier Dulac

@OlivierDulac, bizarre, mon man awkseul donne -F fset--field-separator fs
user1717828

@ user1717828: -F'='ou -F '='sont 2 façons de faire -F fs(fs est "=" dans votre cas). J'ai ajouté les guillemets simples pour m'assurer que le fs est correctement vu et interprété par awk, pas le shell (utile si le fs est ';' par exemple)
Olivier Dulac

4

Vous pouvez essayer ceci:

awk -F"[^0-9]+" '{ sum += $2 } END { print sum+0; }' file

4

Tout le monde a posté des awkréponses impressionnantes , que j'aime beaucoup.

Une variation de @cuonglm remplaçant greppar sed:

sed 's/[^0-9]//g' example.log | paste -sd'+' - | bc
  1. Les sedbandes tout sauf les chiffres.
  2. La paste -sd+ -commande joint toutes les lignes en une seule ligne
  3. Le bcévalue l'expression

3

Vous devez utiliser une calculatrice.

{ tr = \ | xargs printf '[%s=]P%d+p' | dc; } <infile 2>/dev/null

Avec vos quatre lignes qui impriment:

time=31
time=223
time=241
time=784

Et plus simplement:

tr times=c '    + p' <infile |dc

... qui imprime ...

31
223
241
784

Si la vitesse est ce dcque vous recherchez, c'est ce que vous voulez. Il s'agissait traditionnellement bcdu compilateur - et l'est toujours pour de nombreux systèmes.


Pas selon mes mesures : cela dépend du travail que vous avez à faire pour générer la formule
glenn jackman

@glennjackman - vos mesures n'incluent pas dcaussi près que je peux dire. Qu'est-ce que tu racontes?
mikeserv

Soit dit en passant, lorsque vous comparez l'ancien équipage au nouvel équipage - comme lorsque vous comparez avec l' perlensemble d'outils Unix standard - cela n'a vraiment pas beaucoup de sens si vous utilisez des outils GNU compilés sur une chaîne d'outils GNU. Tous les ballonnements qui peuvent affecter négativement les performances de Perl se trouvent également dans tous ces utilitaires GNU compilés par GNU. Triste mais vrai. Vous avez besoin d'un ensemble d'outils réel, simple et simple pour évaluer avec précision la différence. Comme un ensemble d'outils héritage lié statiquement aux bibliothèques musl par exemple - de cette façon, vous pouvez comparer le paradigme un outil / un travail par rapport à celui un outil pour les gouverner tous.
mikeserv

3

Grâce à python3,

import re
with open(file) as f:
    m = f.read()
    l = re.findall(r'\d+', m)
    print(sum(map(int, l)))

re.findallrenvoie une liste de chaînes, cela ne fonctionnera pas
iruvar

@ 1_CR ya, j'oublie ça. Vérifie-le maintenant.
Avinash Raj

C'est peut sum(int(e) for e in l)- être plus pythonique.
cuonglm

3

Solution Pure Bash (Bash 3+):

while IFS= read -r line; do                   # While it reads a line:
    if [[ "$line" =~ [0-9]+ ]]; then      # If the line contains numbers:
        ((counter+=BASH_REMATCH[0]))          # Add the current number to counter
    fi                                    # End if.
done                                  # End loop.

echo "Total number: $counter"         # Print the number.
unset counter                         # Reset counter to 0.

Version courte:

while IFS= read -r l; do [[ "$l" =~ [0-9]+ ]] && ((c+=BASH_REMATCH)); done; echo $c; c=0

1
Peut-être aussi:PS4='$((x+=${time%s*}))' time=0 x=0 sh -x <infile
mikeserv
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.