Comando Shell per sommare numeri interi, uno per riga?


867

Sto cercando un comando che accetti (come input) più righe di testo, ciascuna contenente un singolo numero intero e produca la somma di questi numeri interi.

Come sfondo, ho un file di registro che include misure di temporizzazione. Attraverso il grepping per le righe pertinenti e un po 'di sedriformattazione posso elencare tutti i tempi in quel file. Vorrei calcolare il totale. Posso inoltrare questo output intermedio a qualsiasi comando per fare la somma finale. L'ho sempre usatoexpr in passato, ma a meno che non funzioni in modalità RPN, non penso che riuscirà a farcela (e anche in questo caso sarebbe difficile).

Come posso ottenere la somma degli interi?


2
Questo è molto simile a una domanda ho chiesto qualche tempo fa: stackoverflow.com/questions/295781/...
Andrew

5
Mi piace molto questa domanda per il fatto che ci sono molte possibili risposte corrette (o almeno funzionanti).
Francisco Canedo,

Questa domanda sembra un problema per il golf del codice. codegolf.stackexchange.com :)
Gordon Bean,

Risposte:


1322

Un po 'di imbarazzo dovrebbe farlo?

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

Nota: alcune versioni di awk hanno comportamenti strani se si intende aggiungere qualcosa che supera 2 ^ 31 (2147483647). Vedi i commenti per ulteriori informazioni. Un suggerimento è di utilizzare printfpiuttosto che print:

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

7
C'è molto amore strano in questa stanza! Mi piace come un semplice script come questo possa essere modificato per aggiungere una seconda colonna di dati semplicemente cambiando $ 1 in $ 2
Paul Dixon,

2
Non esiste un limite pratico, poiché elaborerà l'input come flusso. Quindi, se è in grado di gestire un file di linee X, puoi essere abbastanza sicuro di poter gestire X + 1.
Paul Dixon,

4
Una volta ho scritto un rudimentale elaboratore di mailing list con uno script awk eseguito tramite l'utilità ferie. Bei tempi. :)
LS

2
ho appena usato questo per un: contare tutte le pagine dei documenti:ls $@ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk '{s+=$2} END {print s}'
volare pecore il

8
Fai attenzione, non funzionerà con numeri maggiori di 2147483647 (cioè 2 ^ 31), perché awk usa una rappresentazione intera con segno a 32 bit. Usa awk '{s+=$1} END {printf "%.0f", s}' mydatafileinvece.
Giancarlo Sportelli,

665

In genere, Incolla unisce linee di più file, ma può anche essere utilizzata per convertire singole linee 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,

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

1
Molto bella! Avrei messo uno spazio prima del "+", solo per aiutarmi a analizzarlo meglio, ma è stato molto utile per eseguire il piping di alcuni numeri di memoria tramite paste e quindi bc.
Michael H.

73
Molto più facile da ricordare e digitare rispetto alla soluzione awk. Inoltre, tieni presente che pastepuoi utilizzare un trattino -come nome del file, che ti consentirà di reindirizzare i numeri dall'output di un comando all'output standard di paste senza la necessità di creare prima un file:<commands> | paste -sd+ - | bc
George

19
Ho un file con 100 milioni di numeri. Il comando awk richiede 21 secondi; il comando incolla richiede 41 secondi. Ma è bello incontrare 'incolla' comunque!
Abhi,

4
@Abhi: Interessante: suppongo che mi occorrerebbero 20 anni per capire il comando awk in modo che si uniformi anche se non provo 100 milioni e un numero: D
Mark K Cowan,

6
@George Puoi lasciare fuori -, però. (È utile se si desidera combinare un file con stdin).
Alois Mahdal,

128

La versione one-liner in Python:

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

Sopra one-liner non funziona per i file in sys.argv [], ma quello fa stackoverflow.com/questions/450799/…
jfs

È vero- l'autore ha detto che avrebbe indirizzato l'output di un altro script nel comando e stavo cercando di renderlo il più breve possibile :)
dF.

39
La versione più breve sarebbepython -c"import sys; print(sum(map(int, sys.stdin)))"
jfs

4
Adoro questa risposta per la sua facilità di lettura e flessibilità. Avevo bisogno di una dimensione media di file inferiore a 10 Mb in una raccolta di directory e l'ho modificata in questo modo:find . -name '*.epub' -exec stat -c %s '{}' \; | python -c "import sys; nums = [int(n) for n in sys.stdin if int(n) < 10000000]; print(sum(nums)/len(nums))"
Paul Whipp,

1
Puoi anche filtrare i non numeri se hai un po 'di testo mischiato in:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))
Granitosaurus,

91

Vorrei mettere 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 intera con segno a 32 bit: trabocca per somme che superano 2147483647 (ovvero 2 ^ 31).

Una risposta più generale (per sommare numeri interi) sarebbe:

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

Perché printf () aiuta qui? L'overflow di int sarà avvenuto prima perché il codice di somma è lo stesso.
Robert Klemme,

9
Perché il problema è in realtà nella funzione "stampa". Awk utilizza numeri interi a 64 bit, ma per qualche motivo stampa li ridimensiona a 32 bit.
Giancarlo Sportelli,

4
Il bug di stampa sembra essere stato corretto, almeno per awk 4.0.1 e bash 4.3.11, a meno che non mi sbagli: echo -e "2147483647 \n 100" |awk '{s+=$1}END{print s}'spettacoli2147483747
Xen2050,

4
L'uso dei float introduce solo un nuovo problema: echo 999999999999999999 | awk '{s+=$1} END {printf "%.0f\n", s}'produce1000000000000000000
Patrick il

1
L'uso di "% ld" su sistemi a 64 bit non dovrebbe funzionare per non avere printf troncato a 32 bit? Come sottolinea @Patrick, i float non sono un'ottima idea qui.
yerforkferchips,


66
dc -f infile -e '[+z1<r]srz1<rp'

Si noti che i numeri negativi con prefisso con il segno meno devono essere tradotti per dc, poiché utilizza il _prefisso anziché il -prefisso per quello. Ad esempio, tramite tr '-' '_' | dc -f- -e '...'.

Modifica: poiché questa risposta ha ottenuto così tanti voti "per oscurità", ecco una spiegazione dettagliata:

L'espressione [+z1<r]srz1<rp procede come segue :

[   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

Come pseudo-codice:

  1. Definisci "add_top_of_stack" come:
    1. Rimuovere i due valori principali dallo stack e aggiungere nuovamente 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, eseguire "add_top_of_stack"
  3. Stampa il risultato, ora l'unico elemento rimasto nella pila

Per comprendere davvero la semplicità e la potenza di dc, ecco uno script Python funzionante che implementa alcuni dei comandi da dced esegue una versione Python del comando sopra:

### 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
dc è solo lo strumento di scelta da usare. Ma lo farei con un po 'meno operazioni di stack. Supposto che tutte le linee in realtà contengono un numero: (echo "0"; sed 's/$/ +/' inp; echo 'pq')|dc.
Ikrabbe,

5
L'algoritmo on-line: dc -e '0 0 [+?z1<m]dsmxp'. Quindi non salviamo tutti i numeri nello stack prima dell'elaborazione ma li leggiamo ed elaboriamo uno per uno (per essere più precisi, riga per riga, poiché una riga può contenere più numeri). Si noti che una riga vuota può terminare una sequenza di input.
Ruvim,

@ikrabbe è fantastico. In realtà può essere abbreviato da un altro personaggio: lo spazio nella sedsostituzione può essere rimosso, in quanto dc non si preoccupa degli spazi tra argomenti e operatori. (echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc
WhiteHotLoveTiger

58

Con jq :

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

7
Mi piace perché immagino sia così chiaro e breve che potrei davvero essere in grado di ricordarlo.
Alfe,

46

Bash puro e corto.

f=$(cat numbers.txt)
echo $(( ${f//$'\n'/+} ))

9
Questa è la soluzione migliore perché non crea alcun sottoprocesso se si sostituisce la prima riga con f=$(<numbers.txt).
Loentar,

1
alcun modo di avere l'input da stdin? come da una pipa?
njzk2,

@ njzk2 Se inserisci f=$(cat); echo $(( ${f//$'\n'/+} ))uno script, puoi reindirizzare qualsiasi cosa a quello script o invocarlo senza argomenti per l'input interattivo dello stdin (termina con Control-D).
mklement0

5
@loentar <numbers.txtÈ un miglioramento, ma, nel complesso, questa soluzione è efficace solo per piccoli file di input; ad esempio, con un file di 1.000 linee di input la awksoluzione accettata è circa 20 volte più veloce sulla mia macchina - e consuma anche meno memoria, perché il file non viene letto tutto in una volta.
mklement0,

2
Avevo quasi perso la speranza quando ho raggiunto questo. Puro bash!
Omer Akhter,

37
perl -lne '$x += $_; END { print $x; }' < infile.txt

4
E li ho aggiunti di nuovo: "-l" assicura che l'output sia terminato a LF come backtick della shell `e la maggior parte dei programmi prevede, e" <"indica che questo comando può essere utilizzato in una pipeline.
j_random_hacker,

Hai ragione. Come scusa: ogni personaggio di una riga di Perl richiede un lavoro mentale per me, quindi preferisco spogliare il maggior numero possibile di personaggi. L'abitudine era dannosa in questo caso.
jfs,

2
Una delle poche soluzioni che non carica tutto nella RAM.
Erik Aronesty,

28

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

Il mio input potrebbe contenere righe vuote, quindi ho usato ciò che hai pubblicato qui più un grep -v '^$'. Grazie!
James Oravec,

Wow!! la tua risposta è fantastica! il mio personale preferito da tutti nel
passo

Adoro questo e +1 per la pipeline. Soluzione molto semplice e facile per me
Gelin Luo,

24

Ho fatto un rapido benchmark sulle risposte esistenti che

  • usa solo strumenti standard (scusami per cose come luao rocket),
  • sono veri e propri 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, fattibile 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 & 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 immagino che ci sarebbero voluti circa 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

Per fare un confronto, ho compilato la versione C e ho provato anche questo, solo per avere un'idea di quanto siano lente le soluzioni basate su strumenti.

#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

Conclusione

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

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


2
L' approccio paste+ bcera proprio quello che stavo cercando per sommare i valori esadecimali, grazie!
Tomislav Nakic-Alfirevic,

1
Solo per divertimento, a Rust:use std::io::{self, BufRead}; fn main() { let stdin = io::stdin(); let mut sum: i64 = 0; for line in stdin.lock().lines() { sum += line.unwrap().parse::<i64>().unwrap(); } println!("{}", sum); }
Jocelyn,

risposta fantastica. non a nitpick ma è il caso che se decidessi di includere quei risultati di più lunga durata, la risposta sarebbe ancora più fantastica!
Steven Lu,

@StevenLu Ho sentito che la risposta sarebbe stata più lunga e quindi meno fantastica (per usare le tue parole). Ma capisco che questo sentimento non deve essere condiviso da tutti :)
Alfe

Avanti: numba + parallelizzazione
gerrit,

17

Bash semplice una fodera

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

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

7
Nessun gatto necessario: echo $(( $( tr "\n" "+" < /tmp/test) 0 ))
agc

2
trnon è esattamente "plain Bash" / nitpick
Benjamin W.

17

Soluzione BASH, se vuoi rendere questo un comando (ad esempio se devi farlo frequentemente):

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

Quindi utilizzo:

addnums < /tmp/nums

14

Penso che AWK sia ciò che stai cercando:

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

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



11

Il seguente funziona in bash:

I=0

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

echo $I

1
L'espansione dei comandi deve essere utilizzata con cautela quando i file possono essere arbitrariamente grandi. Con numeri.txt di 10 MB, il cat numbers.txtpassaggio sarebbe problematico.
Giacomo,

1
In effetti, comunque (se non fosse per le migliori soluzioni trovate qui) lo userei fino a quando non avessi effettivamente riscontrato quel problema.
Francisco Canedo,

11

Puoi usare num-utils, anche se potrebbe essere eccessivo per quello che ti serve. Questo è un insieme di programmi per manipolare i numeri nella shell e può fare diverse cose ingegnose, incluso ovviamente aggiungerle. È un po 'obsoleto, ma funzionano ancora e possono essere utili se devi fare qualcosa di più.

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


Esempio: numsum numbers.txt.
agc,

9

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 spiego come funziona.


10
Per favore, no. Ci piace far finta che -n -p siano cose semantiche carine, non solo un intelligente incollaggio delle stringhe;)
hobbs

2
Sì, per favore, spiegami :) (Non sono un tipo Perl typea.)
Jens

1
Prova a eseguire "perl -MO = Deparse -lpe '$ c + = $ _} {$ _ = $ c'" e osserva l'output, in sostanza -l usa le nuove righe e entrambi i separatori di input e output, e -p stampa ogni riga. Ma per fare '-p', perl aggiunge prima un po 'di piastra della caldaia (che -MO = Deparse) ti mostrerà, ma poi sostituisce e compila. È quindi possibile inserire un blocco aggiuntivo con la parte '} {' e indurlo a non stampare su ciascuna riga, ma alla fine.
Nym,

9

Bash puro e in una sola riga :-)

$ 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

Perché ci sono due ((parentesi ))?
Atcold,

Bash non proprio puro a causa del gatto. $(< numbers.txt)
rendilo


6

Perl puro alternativo, abbastanza leggibile, non sono richiesti pacchetti o opzioni:

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

o un po 'più breve: perl -e' map {$ x + = $ _} <>; stampa $ x 'infile.txt
Avi Tevet il

La memoria richiesta è quasi 2 GB per un grande input di 10 milioni di numeri
Amit Naidu

6

Per gli amanti del rubino

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

5

Non puoi evitare di inviarlo:

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

Si trova qui: il
più elegante one-liner shell unix per sommare un elenco di numeri di precisione arbitraria?

E qui ci sono i suoi vantaggi speciali rispetto a awk, bc e amici:

  • non dipende dal buffering e quindi non si strozza con input veramente grandi
  • non implica alcuna precisione particolare o dimensione intera per i limiti della materia
  • non è necessario un codice diverso, se è necessario aggiungere numeri in virgola mobile

Includi il codice relativo alla domanda nella risposta e non fare riferimento a un link
Ibo,

5

Utilizzando l' utilità GNU datamash :

seq 10 | datamash sum 1

Produzione:

55

Se i dati di input sono irregolari, con spazi e schede in punti dispari, ciò può confondere datamash, quindi utilizzare l' -Winterruttore:

<commands...> | datamash -W sum 1

... o usa trper pulire gli spazi bianchi:

<commands...> | tr -d '[[:blank:]]' | datamash sum 1


3

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 sottolineato una sceneggiatura da una riga:

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

python -c "dall'input di importazione fileinput; print sum (map (int, input ()))" numbers.txt
jfs

2
cat è abusato, reindirizza stdin dal file: python -c "..." <numbers.txt
Giacomo

2
@rjack: catè usato per dimostrare che lo script funziona sia per stdin che per i file in argv [] (come while(<>)in Perl). Se il tuo input è in un file, allora "<" non è necessario.
jfs,

2
Ma < numbers.txtdimostra che funziona anche su Stdin cat numbers.txt |. E non insegna cattive abitudini.
Xiong Chiamiov,

3
$ cat 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 assorbe il file, quindi non è una buona idea usare su file di grandi dimensioni. Vedi la risposta di j_random_hacker che evita lo slurping.


3

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

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

2
Non hai davvero bisogno della parte {sum = 0}
Uphill_ What '1

3

One-liner in racchetta:

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

3

C (non semplificato)

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)

Ho dovuto votare il commento. Non c'è niente di sbagliato nella risposta - è abbastanza buono. Tuttavia, per dimostrare che il commento rende la risposta fantastica, sto solo votando il commento.
bballdave025

3

Mi scuso in anticipo per la leggibilità dei backtick ("` "), ma questi funzionano in shell diverse da bash e sono quindi più incollabili. Se usi una shell che lo accetta, il formato $ (comando ...) è molto più leggibile (e quindi debuggable) di `comando ...`, quindi sentiti libero di modificare per la tua sanità mentale.

Ho una semplice funzione nel mio bashrc che utilizzerà awk per calcolare una serie di semplici elementi matematici

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

Questo farà +, -, *, /, ^,%, sqrt, sin, cos, parentesi .... (e altro a seconda della tua versione di awk) ... potresti persino immaginarti con printf e il formato in virgola mobile output, ma questo è tutto ciò di cui normalmente ho bisogno

per questa domanda particolare, lo farei semplicemente per ogni riga:

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

quindi il blocco di codice per sommare ogni riga sarebbe simile a questo:

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

Questo se volessi sommarli solo riga per riga. Tuttavia, per un totale di ogni numero nel file di dati

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

a proposito se devo fare qualcosa di veloce sul desktop, uso questo:

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

2
Che tipo di shell antica stai usando che non supporta $()?
nyuszika7h,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.