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!
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
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
La version à une ligne en Python:
$ python -c "import sys; print(sum(int(l) for l in sys.stdin))"
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
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
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 '...'
.
L'expression [+z1<r]srz1<rp
fait 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:
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()
Avec jq :
seq 10 | jq -s 'add' # 'add' is equivalent to 'reduce .[] as $item (0; . + $item)'
Pur et court bash.
f=$(cat numbers.txt)
echo $(( ${f//$'\n'/+} ))
Perl -lne '$x += $_; END { print $x; }' < infile.txt
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
J'ai fait un repère rapide sur les réponses existantes qui
lua
ou rocket
),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:
:; 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
:; seq 100000000 | awk '{s+=$1} END {print s}'
5000000050000000
# 22s
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.
:; seq 100000000 | Perl -lne '$x += $_; END { print $x; }'
5000000050000000
# 15s
:; seq 100000000 | Perl -e 'map {$x += $_} <> and print $x'
5000000050000000
# 48s
:; seq 100000000 | Ruby -e "puts ARGF.map(&:to_i).inject(&:+)"
5000000050000000
# 30s
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
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).
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
Bash un liner
$ cat > /tmp/test
1
2
3
4
5
^D
$ echo $(( $(cat /tmp/test | tr "\n" "+" ) 0 ))
Les travaux suivants en bash:
I=0
for N in `cat numbers.txt`
do
I=`expr $I + $N`
done
echo $I
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.
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.
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.
sed 's/^/.+/' infile | bc | tail -1
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
Pour Ruby Amoureux
Ruby -e "puts ARGF.map(&:to_i).inject(&:+)" numbers.txt
Alternative en Perl pur, assez lisible, aucun paquet ni aucune option requise:
Perl -e "map {$x += $_} <> and print $x" < infile.txt
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:
Ma version:
seq -5 10 | xargs printf "- - %s" | xargs | bc
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()))"
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
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)
Une doublure en raquette:
racket -e '(define (g) (define i (read)) (if (eof-object? i) empty (cons i (g)))) (foldr + 0 (g))' < numlist.txt
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
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
}
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:
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.)
$ 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.
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))))"
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.
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
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 :)
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"
#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';
}
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'
... 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;'