Comando di shell per sumre interi, uno per riga?

Sto cercando un comando che accetti come input più righe di testo, ogni riga contenente un singolo numero intero e restituisca la sum di questi numeri interi.

Come un po ‘di background, ho un file di registro che include le misure di temporizzazione, quindi attraverso l’aghook per le righe rilevanti e un po’ di riformattazione di sed posso elencare tutti i tempi di quel file. Mi piacerebbe calcolare il totale, tuttavia, e la mia mente è diventata vuota in merito a qualsiasi comando che possa canalizzare questo output intermedio per fare la sum finale. Ho sempre usato expr in passato, ma a meno che non funzioni in RPN mode non penso che riuscirà a farcela (e anche allora sarebbe complicato).

Cosa mi manca? Dato che ci sono probabilmente diversi modi per raggiungere questo objective, sarò felice di leggere (e upvote ) qualsiasi approccio che funzioni, anche se qualcun altro ha già pubblicato una soluzione diversa che fa il lavoro.

Domanda correlata: Il comando più breve per calcolare la sum di una colonna di output su Unix? (credits @Andrew )


Aggiornamento : Wow, come previsto ci sono alcune belle risposte qui. Sembra che dovrò sicuramente dare un’ispezione più approfondita come command-line tool in generale!

Un po ‘di awk dovrebbe farlo?

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

Nota: alcune versioni di awk hanno alcuni comportamenti strani se si aggiunge qualcosa che supera il 2 ^ 31 (2147483647). Vedi i commenti per ulteriori informazioni. Un suggerimento è di usare printf piuttosto che print :

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

Incolla tipicamente unisce righe di file multipli, ma può anche essere usato per convertire singole righe di un file in una singola riga. Il flag delimitatore consente di passare un’equazione di tipo x + x a bc.

 paste -s -d+ infile | bc 

In alternativa, quando si esegue il piping da stdin,

  | paste -s -d+ - | bc 

La versione one-liner in Python:

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

Plain 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 

Metterei un grande AVVISO sulla soluzione comunemente approvata:

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

questo perché in questa forma awk utilizza una rappresentazione integer con segno a 32 bit: sarà overflow per somme che superano 2147483647 (cioè 2 ^ 31).

Una risposta più generale (per sumre interi) sarebbe:

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

PS Avrei voluto commentare la prima risposta, ma non ho abbastanza reputazione …

 dc -f infile -e '[+z1 

Notare che i numeri negativi preceduti dal segno meno devono essere tradotti per dc , poiché usa il prefisso _ piuttosto che il prefisso per quello. Ad esempio, tramite tr '-' '_' | dc -f- -e '...' tr '-' '_' | dc -f- -e '...' .

Edit: Dal momento che questa risposta ha ottenuto così tanti voti "per oscurità", ecco una spiegazione dettagliata:

L'espressione [+z1 esegue le seguenti operazioni :

 [ 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  

Come pseudo-codice:

  1. Definisci "add_top_of_stack" come:
    1. Rimuovi i due valori principali dalla pila e aggiungi il risultato
    2. Se lo stack ha due o più valori, eseguire "add_top_of_stack" in modo ricorsivo
  2. Se lo stack ha due o più valori, esegui "add_top_of_stack"
  3. Stampa il risultato, ora l'unico elemento rimasto nella pila

Per capire veramente la semplicità e la potenza di dc , ecco uno script Python funzionante che implementa alcuni dei comandi di dc ed esegue una versione Python del comando precedente:

 ### 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() 

Con jq :

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

Bash pura e breve.

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

I miei quindici centesimi:

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

Esempio:

 $ 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 

Soluzione BASH, se si desidera rendere questo un comando (ad esempio se è necessario farlo frequentemente):

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

Quindi l’uso:

 addnums < /tmp/nums 

Un semplice rivestimento di una fodera

 $ cat > /tmp/test 1 2 3 4 5 ^D $ echo $(( $(cat /tmp/test | tr "\n" "+" ) 0 )) 

I seguenti lavori in bash:

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

Puoi usare num-utils, anche se potrebbe essere eccessivo per quello di cui hai bisogno. Questo è un insieme di programmi per manipolare i numeri nella shell e può fare molte cose belle, incluso, naturalmente, aggiungerle. È un po ‘obsoleto, ma funzionano ancora e possono essere utili se devi fare qualcosa di più.

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

Ho fatto un rapido punto di riferimento sulle risposte esistenti che

  • usa solo strumenti standard (scusa per cose come lua o rocket ),
  • sono veri one-liner,
  • sono in grado di aggiungere enormi quantità di numeri (100 milioni), e
  • sono veloci (ho ignorato quelli che hanno impiegato più di un minuto).

Ho sempre aggiunto i numeri da 1 a 100 milioni che erano fattibili sulla mia macchina in meno di un minuto per diverse soluzioni.

Ecco i risultati:

Pitone

 :; 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 

Incolla e Bc

Questo ha esaurito la memoria sulla mia macchina. Ha funzionato per metà delle dimensioni dell’input (50 milioni di numeri):

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

Quindi suppongo che ci sarebbero voluti ~ 35 secondi per i 100 milioni di numeri.

Perl

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

Rubino

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

C

Solo per fare un paragone ho compilato la versione C e ho testato anche questo, solo per avere un’idea di quanto siano lente le soluzioni basate su strumenti.

 #include  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 

Conclusione

C è ovviamente il più veloce con 8s, ma la soluzione Pypy aggiunge solo un piccolo overhead di circa il 30% agli 11 secondi . Ma, per essere onesti, Pypy non è esattamente standard. La maggior parte delle persone ha solo CPython installato che è significativamente più lento (22s), esattamente veloce come la famosa soluzione Awk.

La soluzione più veloce basata su strumenti standard è Perl (15 s).

Mi rendo conto che questa è una vecchia domanda, ma mi piace questa soluzione abbastanza per condividerla.

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

Se c’è interesse, ti spiegherò come funziona.

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

Pure bash e in una sola linea 🙂

 $ 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 

Perl puro alternativo, abbastanza leggibile, senza pacchetti o opzioni richieste:

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

Per gli amanti del ruby

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

Penso che AWK sia quello che stai cercando:

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

È ansible utilizzare questo comando passando l’elenco dei numeri attraverso l’input standard o passando il file contenente i numeri come parametro.

La mia versione:

 seq -5 10 | xargs printf "- - %s" | xargs | bc 

Puoi farlo in python, se ti senti a tuo agio:

Non testato, appena digitato:

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

Sebastian ha indicato una sceneggiatura:

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

Il seguente dovrebbe funzionare (assumendo che il tuo numero sia il secondo campo su ogni riga).

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

One-liner in racket:

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

C (non semplificato)

 seq 1 10 | tcc -run < (cat << EOF #include  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) 
 $ gatto n
 2
 4
 2
 7
 8
 9
 $ perl -MList::Util -le 'print List::Util::sum(<>)' < n 32 

Oppure puoi digitare i numeri sulla riga di comando:

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

Tuttavia, questo slurps il file, quindi non è una buona idea usare su file di grandi dimensioni. Vedi la risposta di j_random_hacker che evita di inghiottire.

Sommario in tempo reale per consentire di monitorare i progressi di alcuni compiti di sgranatura numerica.

 $ 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 

(Non è necessario impostare $total a zero in questo caso. Né è ansible accedere a $ totale dopo la fine.)

Puoi usare il tuo comando ‘expr’ preferito solo per impreziosire un poco l’input:

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

Il processo è:

  • “tr” sostituisce i caratteri di eoln con un simbolo +,
  • sed calcola il ‘+’ con spazi su ciascun lato, e poi toglie il + finale dalla linea
  • xargs inserisce l’input convogliato nella riga di comando affinché expr consumi.

C ++ (semplificato):

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

Progetto SCC – http://volnitsky.com/project/scc/

SCC è un analizzatore di snippet C ++ al prompt della shell