web-dev-qa-db-fra.com

Commande Shell pour additionner les entiers, un par ligne?

Je recherche une commande qui accepte en entrée plusieurs lignes de texte, chaque ligne contenant un seul entier, et génère la somme de ces entiers.

En guise d’arrière-plan, j’ai un fichier journal qui inclut les mesures de minutage. Ainsi, grâce à la recherche de lignes pertinentes et à un peu de reformatage sed, je peux répertorier tous les chronométrages contenus dans ce fichier. J'aimerais cependant calculer le total et mon esprit est resté vide en ce qui concerne toute commande à laquelle je peux diriger cette sortie intermédiaire afin de faire la somme finale. J'ai toujours utilisé expr dans le passé, mais à moins que le logiciel ne fonctionne en mode RPN, je ne pense pas que cela résoudra ce problème (et même dans ce cas, ce serait délicat).

Qu'est-ce que je rate? Étant donné qu’il existe probablement plusieurs moyens d’y parvenir, je me ferai un plaisir de lire (et d’augmenter le vote) toute approche qui fonctionne, même si une autre personne a déjà publié une solution différente qui fait le travail.

Question associée: La commande la plus courte permettant de calculer la somme d'une colonne de sortie sous Unix? (crédits @ Andrew )


Mise à jour : Wow, comme prévu, il existe quelques bonnes réponses ici. On dirait que je vais certainement devoir donner awk une inspection plus approfondie en tant qu'outil de ligne de commande en général!

783
Andrzej Doyle

Un peu d'awk devrait le faire?

awk '{s+=$1} END {print s}' mydatafile

Remarque: certaines versions de awk ont ​​des comportements étranges si vous allez ajouter quelque chose qui dépasse 2 ^ 31 (2147483647). Voir les commentaires pour plus de fond. Une suggestion est d'utiliser printf plutôt que print:

awk '{s+=$1} END {printf "%.0f", s}' mydatafile
1219
Paul Dixon

Le collage fusionne généralement les lignes de plusieurs fichiers, mais il peut également être utilisé pour convertir des lignes individuelles d'un fichier en une seule ligne. L'indicateur de délimiteur vous permet de transmettre une équation de type x + x à bc.

paste -s -d+ infile | bc

Alternativement, lorsque vous passez de stdin,

<commands> | paste -s -d+ - | bc
632
radoulov

La version à une ligne en Python:

$ python -c "import sys; print(sum(int(l) for l in sys.stdin))"
117
dF.

Je mettrais un gros AVERTISSEMENT sur la solution généralement approuvée:

awk '{s+=$1} END {print s}' mydatafile # DO NOT USE THIS!!

en effet, sous cette forme, awk utilise une représentation entière signée sur 32 bits: il débordera pour les sommes supérieures à 2147483647 (c'est-à-dire, 2 ^ 31).

Une réponse plus générale (pour additionner des entiers) serait:

awk '{s+=$1} END {printf "%.0f\n", s}' mydatafile # USE THIS INSTEAD
78
Giancarlo Sportelli

Bash simple:

$ cat numbers.txt 
1
2
3
4
5
6
7
8
9
10
$ sum=0; while read num; do ((sum += num)); done < numbers.txt; echo $sum
55
73
Giacomo
dc -f infile -e '[+z1<r]srz1<rp'

Notez que les nombres négatifs précédés du signe moins doivent être traduits pour dc, car ils utilisent le préfixe _ plutôt que le préfixe -. Par exemple, via tr '-' '_' | dc -f- -e '...'.

Edit: Puisque cette réponse a eu tant de voix "pour l'obscurité", voici une explication détaillée:

L'expression [+z1<r]srz1<rpfait ce qui suit :

[   interpret everything to the next ] as a string
  +   Push two values off the stack, add them and Push the result
  z   Push the current stack depth
  1   Push one
  <r  pop two values and execute register r if the original top-of-stack (1)
      is smaller
]   end of the string, will Push the whole thing to the stack
sr  pop a value (the string above) and store it in register r
z   Push the current stack depth again
1   Push 1
<r  pop two values and execute register r if the original top-of-stack (1)
    is smaller
p   print the current top-of-stack

En pseudo-code:

  1. Définissez "add_top_of_stack" comme suit:
    1. Supprimez les deux valeurs supérieures de la pile et rajoutez le résultat
    2. Si la pile a deux valeurs ou plus, exécutez "add_top_of_stack" de manière récursive
  2. Si la pile a deux valeurs ou plus, exécutez "add_top_of_stack"
  3. Imprimer le résultat, maintenant le seul élément restant dans la pile

Pour vraiment comprendre la simplicité et la puissance de dc, voici un script fonctionnel Python qui implémente certaines des commandes de dc et exécute une version Python de la commande ci-dessus:

### Implement some commands from dc
registers = {'r': None}
stack = []
def add():
    stack.append(stack.pop() + stack.pop())
def z():
    stack.append(len(stack))
def less(reg):
    if stack.pop() < stack.pop():
        registers[reg]()
def store(reg):
    registers[reg] = stack.pop()
def p():
    print stack[-1]

### Python version of the dc command above

# The equivalent to -f: read a file and Push every line to the stack
import fileinput
for line in fileinput.input():
    stack.append(int(line.strip()))

def cmd():
    add()
    z()
    stack.append(1)
    less('r')

stack.append(cmd)
store('r')
z()
stack.append(1)
less('r')
p()
63
CB Bailey

Avec jq :

seq 10 | jq -s 'add' # 'add' is equivalent to 'reduce .[] as $item (0; . + $item)'
53
banyan

Pur et court bash.

f=$(cat numbers.txt)
echo $(( ${f//$'\n'/+} ))
45
Daniel
Perl -lne '$x += $_; END { print $x; }' < infile.txt
36
j_random_hacker

Mes quinze cents:

$ cat file.txt | xargs  | sed -e 's/\ /+/g' | bc

Exemple:

$ cat text
1
2
3
3
4
5
6
78
9
0
1
2
3
4
576
7
4444
$ cat text | xargs  | sed -e 's/\ /+/g' | bc 
5148
27
innocent-world

J'ai fait un repère rapide sur les réponses existantes qui

  • n'utilisez que des outils standard (désolé pour des choses comme lua ou rocket),
  • sont de véritables one-liners,
  • sont capables d’ajouter d’énormes quantités de chiffres (100 millions), et
  • sont rapides (j'ai ignoré ceux qui ont pris plus d'une minute).

J'ai toujours ajouté les nombres de 1 à 100 millions qui étaient réalisables sur ma machine en moins d'une minute pour plusieurs solutions.

Voici les résultats:

Python

:; seq 100000000 | python -c 'import sys; print sum(map(int, sys.stdin))'
5000000050000000
# 30s
:; seq 100000000 | python -c 'import sys; print sum(int(s) for s in sys.stdin)'
5000000050000000
# 38s
:; seq 100000000 | python3 -c 'import sys; print(sum(int(s) for s in sys.stdin))'
5000000050000000
# 27s
:; seq 100000000 | python3 -c 'import sys; print(sum(map(int, sys.stdin)))'
5000000050000000
# 22s
:; seq 100000000 | pypy -c 'import sys; print(sum(map(int, sys.stdin)))'
5000000050000000
# 11s
:; seq 100000000 | pypy -c 'import sys; print(sum(int(s) for s in sys.stdin))'
5000000050000000
# 11s

Awk

:; seq 100000000 | awk '{s+=$1} END {print s}'
5000000050000000
# 22s

Coller & Bc

Cela a manqué de mémoire sur ma machine. Cela a fonctionné pour la moitié de la taille de l'entrée (50 millions de chiffres):

:; seq 50000000 | paste -s -d+ - | bc
1250000025000000
# 17s
:; seq 50000001 100000000 | paste -s -d+ - | bc
3750000025000000
# 18s

Je suppose donc qu’il aurait fallu environ 35 secondes pour les 100 millions de chiffres.

Perl

:; seq 100000000 | Perl -lne '$x += $_; END { print $x; }'
5000000050000000
# 15s
:; seq 100000000 | Perl -e 'map {$x += $_} <> and print $x'
5000000050000000
# 48s

Ruby

:; seq 100000000 | Ruby -e "puts ARGF.map(&:to_i).inject(&:+)"
5000000050000000
# 30s

C

Juste à titre de comparaison, j’ai compilé la version C et l’ai également testé, pour avoir une idée de la lenteur des solutions basées sur les outils.

#include <stdio.h>
int main(int argc, char** argv) {
    long sum = 0;
    long i = 0;
    while(scanf("%ld", &i) == 1) {
        sum = sum + i;
    }
    printf("%ld\n", sum);
    return 0;
}

:; seq 100000000 | ./a.out 
5000000050000000
# 8s

Conclusion

C est bien sûr le plus rapide avec 8s, mais la solution Pypy n’ajoute que très peu de frais généraux d’environ 30% à 11s. Mais, pour être juste, Pypy n'est pas exactement la norme. La plupart des gens n’ont installé que CPython, ce qui est nettement plus lent (22 secondes), exactement aussi vite que la solution Awk populaire.

La solution la plus rapide basée sur des outils standard est Perl (15s).

19
Alfe

Solution BASH, si vous souhaitez en faire une commande (par exemple, si vous devez le faire fréquemment):

addnums () {
  local total=0
  while read val; do
    (( total += val ))
  done
  echo $total
}

Puis utilisation:

addnums < /tmp/nums
17
Jay

Bash un liner

$ cat > /tmp/test
1 
2 
3 
4 
5
^D

$ echo $(( $(cat /tmp/test | tr "\n" "+" ) 0 ))
16
Khaja Minhajuddin

Les travaux suivants en bash:

I=0

for N in `cat numbers.txt`
do
    I=`expr $I + $N`
done

echo $I
11
Francisco Canedo

Vous pouvez utiliser num-utils, bien que cela puisse être excessif pour ce dont vous avez besoin. Il s’agit d’un ensemble de programmes permettant de manipuler des nombres dans le shell et pouvant effectuer plusieurs opérations astucieuses, y compris bien sûr leur addition. C'est un peu démodé, mais ils fonctionnent toujours et peuvent être utiles si vous avez besoin de faire quelque chose de plus.

http://suso.suso.org/programs/num-utils/

11
sykora

Je pense que AWK est ce que vous recherchez:

awk '{sum+=$1}END{print sum}'

Vous pouvez utiliser cette commande en passant la liste des nombres via l'entrée standard ou en transmettant le fichier contenant les nombres en tant que paramètre.

11
Paolo

Je réalise que c'est une vieille question, mais j'aime suffisamment cette solution pour la partager.

% cat > numbers.txt
1 
2 
3 
4 
5
^D
% cat numbers.txt | Perl -lpe '$c+=$_}{$_=$c'
15

S'il y a un intérêt, je vais expliquer comment ça marche.

9
Nym
sed 's/^/.+/' infile | bc | tail -1
9
Dominique

Pure bash et dans un one-liner :-)

$ cat numbers.txt
1
2
3
4
5
6
7
8
9
10


$ I=0; for N in $(cat numbers.txt); do I=$(($I + $N)); done; echo $I
55
8
Oliver Ertl

Pour Ruby Amoureux

Ruby -e "puts ARGF.map(&:to_i).inject(&:+)" numbers.txt
6
johnlinvc

Alternative en Perl pur, assez lisible, aucun paquet ni aucune option requise:

Perl -e "map {$x += $_} <> and print $x" < infile.txt
6
clint

Impossible d'éviter de soumettre ceci:

jot 1000000 | sed '2,$s/$/+/;$s/$/p/' | dc

Il se trouve ici:
Le plus élégant unix Shell unix pour résumer une liste de nombres de précision arbitraire?

Et voici ses avantages par rapport à awk, bc et ses amis:

  • il ne dépend pas de la mise en mémoire tampon et ne s'étouffe donc pas avec de très grandes entrées
  • cela n'implique pas de précision particulière - ni de taille entière pour cette question - limite
  • pas besoin de code différent, si des nombres en virgule flottante doivent être ajoutés
4
fgeorgatos

Ma version:

seq -5 10 | xargs printf "- - %s" | xargs  | bc
4
Vytenis Bivainis

Vous pouvez le faire en python, si vous vous sentez à l'aise:

Non testé, juste tapé:

out = open("filename").read();
lines = out.split('\n')
ints = map(int, lines)
s = sum(ints)
print s

Sebastian a souligné un script à une ligne:

cat filename | python -c"from fileinput import input; print sum(map(int, input()))"
3
Tiago

Ce qui suit devrait fonctionner (en supposant que votre numéro est le deuxième champ de chaque ligne).

awk 'BEGIN {sum=0} \
 {sum=sum + $2} \
END {print "tot:", sum}' Yourinputfile.txt
3
James Anderson

C (not simplified)

seq 1 10 | tcc -run <(cat << EOF
#include <stdio.h>
int main(int argc, char** argv) {
    int sum = 0;
    int i = 0;
    while(scanf("%d", &i) == 1) {
        sum = sum + i;
    }
    printf("%d\n", sum);
    return 0;
}
EOF)
3
Greg Bowyer

Une doublure en raquette:

racket -e '(define (g) (define i (read)) (if (eof-object? i) empty (cons i (g)))) (foldr + 0 (g))' < numlist.txt
3
b2coutts

C++ (simplifié):

echo {1..10} | scc 'WRL n+=$0; n'

Projet SCC - http://volnitsky.com/project/scc/

SCC est un évaluateur d'extraits de code C++ à l'invite du shell

2
Leonid Volnitsky

Veuillez nous excuser par avance pour la lisibilité des backticks ("` "), mais ceux-ci fonctionnent dans des coquilles autres que bash et sont donc plus faciles à coller. Si vous utilisez un shell qui l'accepte, le format $ (command ...) est beaucoup plus lisible (et donc débogable) que `command ...`, n'hésitez donc pas à le modifier pour votre santé.

J'ai une fonction simple dans mon bash qui utilisera awk pour calculer un nombre d'éléments mathématiques simples

calc(){
  awk 'BEGIN{print '"$@"' }'
}

Cela fera +, -, *, /, ^,%, sqrt, sin, cos, parenthèse .... (et plus en fonction de votre version de awk) ... vous pourriez même vous faire plaisir avec printf et le format en virgule flottante sortie, mais c'est tout ce dont j'ai besoin normalement

pour cette question particulière, je le ferais simplement pour chaque ligne:

calc `echo "$@"|tr " " "+"`

donc le bloc de code pour additionner chaque ligne ressemblerait à ceci:

while read LINE || [ "$LINE" ]; do
  calc `echo "$LINE"|tr " " "+"` #you may want to filter out some lines with a case statement here
done

C'est si vous vouliez seulement les additionner ligne par ligne. Cependant pour un total de chaque nombre dans le fichier de données

VARS=`<datafile`
calc `echo ${VARS// /+}`

d'ailleurs si j'ai besoin de faire quelque chose de rapide sur le bureau, j'utilise ceci:

xcalc() { 
  A=`calc "$@"`
  A=`Xdialog --stdout --inputbox "Simple calculator" 0 0 $A`
  [ $A ] && xcalc $A
}
2
technosaurus

Vous pouvez utiliser votre commande 'expr' préférée dont vous avez besoin pour finaliser un peu la saisie:

seq 10 | tr '[\n]' '+' | sed -e 's/+/ + /g' -e's/ + $/\n/' | xargs expr

Le processus est:

  • "tr" remplace les caractères eoln par un symbole +,
  • sed remplit le '+' avec des espaces de chaque côté, puis enlève le dernier + de la ligne
  • xargs insère l'entrée redirigée dans la ligne de commande pour que expr puisse la consommer.
2
Alan Dyke

La sommation en temps réel vous permet de surveiller la progression d'une tâche de calcul.

$ cat numbers.txt 
1
2
3
4
5
6
7
8
9
10

$ cat numbers.txt | while read new; do total=$(($total + $new)); echo $total; done
1
3
6
10
15
21
28
36
45
55

(Il n'est pas nécessaire de régler $total à zéro dans ce cas. Vous ne pouvez pas non plus accéder à $ total après la fin.)

2
sanmai
 $ cat 
 2 
 4 
 2 
 7 
 8 
 9 
$ Perl -MList::Util -le 'print List::Util::sum(<>)' < n
32

Ou, vous pouvez taper les numéros sur la ligne de commande:

$ Perl -MList::Util -le 'print List::Util::sum(<>)'
1
3
5
^D
9

Cependant, celui-ci lit le fichier, ce n’est donc pas une bonne idée de l’utiliser sur des fichiers volumineux. Voir réponse de j_random_hacker qui évite les slurping.

2
Sinan Ünür

Juste pour être complet, il existe aussi une solution R

seq 1 10 | R -q -e "f <- file('stdin'); open(f); cat(sum(as.numeric(readLines(f))))"
1
drmariod

Utilisation de la variable env tmp

tmp=awk -v tmp="$tmp" '{print $tmp" "$1}' <filename>|echo $tmp|sed "s/ /+/g"|bc

tmp=cat <filename>|awk -v tmp="$tmp" '{print $tmp" "$1}'|echo $tmp|sed "s/ /+/g"|bc

Merci.

1
김상헌

Vous pouvez le faire avec Alacon - utilitaire de ligne de commande pour la base de données Alasql .

Cela fonctionne avec Node.js, vous devez donc installer le paquet Node.js puis Alasql :

Pour calculer la somme à partir de stdin, vous pouvez utiliser la commande suivante:

> cat data.txt | node alacon "SELECT VALUE SUM([0]) FROM TXT()" >b.txt
1
agershun

Un interprète lua est présent sur tous les systèmes basés sur Fedora [Fedora, RHEL, CentOS, Korora, etc., car il est intégré à rpm-package (le package du gestionnaire de packages rpm), c.-à-d. Rpm-lua] et si vous souhaitez apprendre lua ce genre de problèmes est idéal (vous travaillerez aussi bien).

cat filname | lua -e "sum = 0;for i in io.lines() do sum=sum+i end print(sum)"

et il fonctionne. Lua est commentée, vous devrez peut-être subir des blessures répétées au clavier :)

1
fedvasu

une solution simple serait d'écrire un programme pour le faire pour vous. Cela pourrait probablement être fait assez rapidement en python, quelque chose comme:

sum = 0
file = open("numbers.txt","R")
for line in file.readlines(): sum+=int(line)
file.close()
print sum

Je n'ai pas testé ce code, mais ça a l'air correct. Modifiez simplement numbers.txt avec le nom du fichier, enregistrez le code dans un fichier nommé sum.py et, dans le type de console, dans "python sum.py"

0
Matt Boehm
#include <iostream>

int main()
{
    double x = 0, total = 0;
    while (std::cin >> x)
        total += x;
    if (!std::cin.eof())
        return 1;
    std::cout << x << '\n';
}
0
Tony Delroy

Utiliser le GNUdatamash util :

seq 10 | datamash sum 1

Sortie:

55
0
agc

Une ligne à Rebol:

rebol -q --do 's: 0 while [d: input] [s: s + to-integer d] print s' < infile.txt

Malheureusement, ce qui précède ne fonctionne pas encore dans Rebol 3 (INPUT ne diffuse pas STDIN).

Voici donc une solution intermédiaire qui fonctionne également dans Rebol 3:

rebol -q --do 's: 0 foreach n to-block read %infile.txt [s: s + n] print s'
0
draegtun

... et la version PHP, par souci d'exhaustivité

cat /file/with/numbers | php -r '$s = 0; while (true) { $e = fgets(STDIN); if (false === $e) break; $s += $e; } echo $s;'
0
Ivan Krechetov