957 votes

Commande Shell pour additionner des nombres entiers, un par ligne ?

Je cherche une commande qui accepte (en entrée) plusieurs lignes de texte, chaque ligne contenant un seul entier, et qui affiche la somme de ces entiers.

Pour la petite histoire, j'ai un fichier journal qui contient des mesures de temps. En recherchant les lignes pertinentes et en faisant un peu de sed En reformatant le fichier, je peux énumérer toutes les durées qu'il contient. Je voudrais faire le total. Je peux acheminer cette sortie intermédiaire vers n'importe quelle commande afin d'effectuer la somme finale. J'ai toujours utilisé expr dans le passé, mais à moins qu'il ne fonctionne en mode RPN, je ne pense pas qu'il puisse faire face à cette situation (et même dans ce cas, ce serait délicat).

Comment obtenir la somme de nombres entiers ?

2 votes

Cette question est très proche de celle que j'ai posée il y a quelque temps : stackoverflow.com/questions/295781/

5 votes

J'aime beaucoup cette question parce qu'elle offre de nombreuses possibilités de réponses correctes (ou du moins valables).

0 votes

Cette question ressemble à un problème de golf codé. codegolf.stackexchange.com :)

1459voto

Paul Dixon Points 122033

Un peu d'awk devrait suffire ?

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

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

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

11 votes

Il y a beaucoup d'amour pour awk dans cette pièce ! J'aime comment un simple script comme celui-ci peut être modifié pour ajouter une deuxième colonne de données en changeant simplement le $1 en $2

0 votes

Quelles sont les limites de l'awk, c'est-à-dire combien d'éléments de données peut-il traiter avant de mourir, ou est-il préférable d'utiliser un petit extrait de texte en c ?

2 votes

Il n'y a pas de limite pratique, puisqu'il traitera l'entrée comme un flux. Donc, s'il peut traiter un fichier de X lignes, vous pouvez être sûr qu'il peut traiter X+1.

714voto

radoulov Points 1551

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

paste -s -d+ infile | bc

Alternativement, lors de l'utilisation de la pipette à partir de stdin,

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

1 votes

C'est très bien ! J'aurais mis un espace avant le "+", juste pour m'aider à mieux l'analyser, mais c'était très pratique pour faire passer quelques nombres de mémoire dans paste & puis bc.

0 votes

Une solution utilisant bc ou dc est ce que je cherchais comme solution à mon propre problème similaire. Je ne connais pas grand-chose à ce sujet et il semble qu'il soit sous-utilisé, il est donc très bon de voir que d'autres savent comment l'utiliser. Je pense qu'il est beaucoup plus rapide que les autres solutions, surtout s'il y a beaucoup de lignes dans l'entrée.

80 votes

Beaucoup plus facile à mémoriser et à taper que la solution awk. Notez également que paste peut utiliser un tiret - comme nom de fichier - ce qui vous permettra d'envoyer les nombres de la sortie d'une commande vers la sortie standard de paste sans avoir à créer un fichier au préalable : <commands> | paste -sd+ - | bc

141voto

dF. Points 29787

La version en ligne en Python :

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

0 votes

La ligne ci-dessus ne fonctionne pas pour les fichiers dans sys.argv[], mais celle-ci fonctionne stackoverflow.com/questions/450799/

0 votes

C'est vrai - l'auteur a dit qu'il allait insérer la sortie d'un autre script dans la commande et j'essayais de la rendre aussi courte que possible :)

44 votes

La version abrégée serait python -c"import sys; print(sum(map(int, sys.stdin)))"

86voto

rjack Points 3300

Simple bash :

$ 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

2 votes

0 votes

@rjack, où se trouve num défini ? Je crois qu'il est relié d'une manière ou d'une autre au < numbers.txt mais on ne sait pas exactement comment.

67voto

Charles Bailey Points 244082
dc -f infile -e '[+z1<r]srz1<rp'

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

Edit : Puisque cette réponse a reçu tant de votes "pour l'obscurité", voici une explication détaillé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

Sous forme de pseudo-code :

  1. Définir "add_top_of_stack" comme :
    1. Retirer les deux valeurs supérieures de la pile et ajouter le résultat.
    2. Si la pile comporte deux valeurs ou plus, exécuter "add_top_of_stack" de manière récursive.
  2. Si la pile comporte deux valeurs ou plus, exécuter "add_top_of_stack"
  3. Imprimer le résultat, qui est maintenant le seul élément restant dans la pile.

Pour bien comprendre la simplicité et la puissance de dc Voici un script Python fonctionnel qui met en œuvre certaines des commandes de la rubrique 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()

2 votes

Dc est simplement l'outil de choix à utiliser. Mais je le ferais avec un peu moins d'opérations d'empilage. Je suppose que toutes les lignes contiennent réellement un nombre : (echo "0"; sed 's/$/ +/' inp; echo 'pq')|dc .

5 votes

L'algorithme en ligne : dc -e '0 0 [+?z1<m]dsmxp' . Nous ne sauvegardons donc pas tous les nombres sur la pile avant de les traiter, mais nous les lisons et les traitons un par un (pour être plus précis, ligne par ligne, puisqu'une ligne peut contenir plusieurs nombres). Notez qu'une ligne vide peut mettre fin à une séquence d'entrée.

0 votes

@ikrabbe c'est super. Il peut en fait être raccourci d'un caractère supplémentaire : l'espace dans le champ sed peut être supprimée, car dc ne se soucie pas des espaces entre les arguments et les opérateurs. (echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X