Comando Shell para somar números inteiros, um por linha?


867

Estou procurando um comando que aceite (como entrada) várias linhas de texto, cada linha contendo um único inteiro e produza a soma desses números inteiros.

Como pano de fundo, tenho um arquivo de log que inclui medições de tempo. Através do grepping para as linhas relevantes e um pouco de sedreformatação, posso listar todas as temporizações nesse arquivo. Eu gostaria de calcular o total. Eu posso canalizar essa saída intermediária para qualquer comando para fazer a soma final. Eu sempre usei exprno passado, mas, a menos que seja executado no modo RPN, não acho que vai lidar com isso (e mesmo assim seria complicado).

Como posso obter a soma dos números inteiros?


2
Isso é muito semelhante a uma pergunta que fiz há um tempo: stackoverflow.com/questions/295781/…
An̲̳̳drew

5
Eu realmente gosto dessa pergunta pelo fato de haver muitas respostas corretas possíveis (ou pelo menos funcionando).
Francisco Canedo

Esta questão parece um problema para o código de golfe. codegolf.stackexchange.com :)
Gordon Bean

Respostas:


1322

Um pouco de awk deve fazer isso?

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

Nota: algumas versões do awk têm comportamentos estranhos se você adicionar algo que exceda 2 ^ 31 (2147483647). Veja os comentários para mais informações. Uma sugestão é usar em printfvez de print:

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

7
Há muito amor estranho nesta sala! Eu gosto de como um script simples como este poderia ser modificado para adicionar uma segunda coluna de dados apenas mudando os US $ 1 a US $ 2
Paul Dixon

2
Não há um limite prático, pois ele processará a entrada como um fluxo. Portanto, se ele pode manipular um arquivo de linhas X, você pode ter certeza de que ele pode manipular X + 1.
Paul Dixon

4
Certa vez, escrevi um processador rudimentar de listas de discussão com um script awk executado por meio do utilitário de férias. Bons tempos. :) #
31712 LS

2
apenas usei isso para a: conte o script de todas as páginas dos documentos:ls $@ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk '{s+=$2} END {print s}'
flying sheep o

8
Cuidado, ele não funcionará com números maiores que 2147483647 (ou seja, 2 ^ 31), porque o awk usa uma representação inteira com sinal de 32 bits. Use em awk '{s+=$1} END {printf "%.0f", s}' mydatafilevez disso.
Giancarlo Sportelli

665

Colar normalmente mescla linhas de vários arquivos, mas também pode ser usado para converter linhas individuais de um arquivo em uma única linha. O sinalizador delimitador permite passar uma equação do tipo x + x para bc.

paste -s -d+ infile | bc

Como alternativa, ao canalizar a partir de stdin,

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

1
Muito agradável! Eu teria colocado um espaço antes do "+", apenas para me ajudar a analisá-lo melhor, mas isso foi muito útil para canalizar alguns números de memória através de colar e depois em bc.
Michael H.

73
Muito mais fácil de lembrar e digitar do que a solução awk. Além disso, nota que pastepode usar um traço -como o nome do arquivo - o que lhe permitirá canalizar os números da saída de um comando para a saída padrão da pasta, sem a necessidade de criar um arquivo primeiro:<commands> | paste -sd+ - | bc
George

19
Eu tenho um arquivo com 100 milhões de números. O comando awk leva 21s; o comando colar leva 41s. Mas é bom conhecer 'colar', no entanto!
Abhi

4
@Abhi: Interessante: DI acho que ele me levaria 20 anos para descobrir o comando awk por isso equilibra embora até que eu tente 100 milhões e um números: D
Mark K Cowan

6
@ George Você pode deixar de fora o -, porém. (É útil se você quiser combinar um arquivo com o stdin).
Alois Mahdal

128

A versão one-liner no Python:

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

Acima de uma linha não funciona para arquivos em sys.argv [], mas que se faz stackoverflow.com/questions/450799/...
jfs

É verdade que o autor disse que iria canalizar a saída de outro script para o comando e eu estava tentando torná-lo o mais curto possível :)
dF.

39
Versão mais curta seriapython -c"import sys; print(sum(map(int, sys.stdin)))"
jfs 17/01

4
Eu amo essa resposta por sua facilidade de leitura e flexibilidade. Eu precisava do tamanho médio dos arquivos com menos de 10 Mb em uma coleção de diretórios e modificou-lo para isto: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
Você também pode filtrar não números se tiver algum texto misturado em:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))
Granitosaurus

91

Eu colocaria um grande aviso sobre a solução comumente aprovada:

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

isso ocorre porque, neste formulário, o awk usa uma representação inteira assinada de 32 bits: ele excederá as somas que excedem 2147483647 (ou seja, 2 ^ 31).

Uma resposta mais geral (para somar números inteiros) seria:

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

Por que printf () ajuda aqui? O estouro do int terá acontecido antes disso porque o código de soma é o mesmo.
Robert Klemme

9
Porque o problema está realmente na função "imprimir". O Awk usa números inteiros de 64 bits, mas, por algum motivo, a impressão é reduzida para 32 bits.
Giancarlo Sportelli

4
O erro de impressão parece estar corrigido, pelo menos para o awk 4.0.1 e o bash 4.3.11, a menos que eu esteja enganado: echo -e "2147483647 \n 100" |awk '{s+=$1}END{print s}'shows2147483747
Xen2050

4
Usando carros alegóricos apenas introduz um novo problema: echo 999999999999999999 | awk '{s+=$1} END {printf "%.0f\n", s}'produz1000000000000000000
Patrick

1
O uso de "% ld" em sistemas de 64 bits não deveria funcionar para não ter o printf truncado em 32 bits? Como @Patrick aponta, carros alegóricos não são uma ótima idéia aqui.
Yerforkferchips


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

Observe que números negativos prefixados com sinal de menos devem ser traduzidos dc, pois ele usa _prefixo em vez de -prefixo para isso. Por exemplo, via tr '-' '_' | dc -f- -e '...'.

Edit: Como esta resposta obteve tantos votos "pela obscuridade", aqui está uma explicação detalhada:

A expressão [+z1<r]srz1<rp faz o seguinte :

[   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

Como pseudo-código:

  1. Defina "add_top_of_stack" como:
    1. Remova os dois principais valores da pilha e adicione o resultado novamente
    2. Se a pilha tiver dois ou mais valores, execute "add_top_of_stack" recursivamente
  2. Se a pilha tiver dois ou mais valores, execute "add_top_of_stack"
  3. Imprima o resultado, agora o único item restante na pilha

Para realmente entender a simplicidade e o poder de dc, aqui está um script Python funcional que implementa alguns dos comandos dce executa uma versão Python do comando acima:

### 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 é apenas a ferramenta de escolha para usar. Mas eu faria isso com um pouco menos de pilha. Assumindo que todas as linhas realmente conter um número: (echo "0"; sed 's/$/ +/' inp; echo 'pq')|dc.
ikrabbe

5
O algoritmo on-line: dc -e '0 0 [+?z1<m]dsmxp'. Portanto, não salvamos todos os números na pilha antes do processamento, mas os lemos e processamos um a um (para ser mais preciso, linha por linha, pois uma linha pode conter vários números). Observe que a linha vazia pode terminar uma sequência de entrada.
Ruvim 02/10/2015

@ikrabbe isso é ótimo. Na verdade, pode ser reduzido por mais um caractere: o espaço na sedsubstituição pode ser removido, pois dc não se importa com espaços entre argumentos e operadores. (echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc
WhiteHotLoveTiger

58

Com jq :

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

7
Gosto disso porque acho que é tão claro e curto que posso ser capaz de me lembrar.
Alfe

46

Festança pura e curta.

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

9
Essa é a melhor solução, pois não cria nenhum subprocesso se você substituir a primeira linha por f=$(<numbers.txt).
loentar

1
alguma maneira de ter a entrada de stdin? como de um cachimbo?
Njzk2

@ njzk2 Se você inserir f=$(cat); echo $(( ${f//$'\n'/+} ))um script, poderá canalizar qualquer coisa para esse script ou invocá-lo sem argumentos para a entrada stdin interativa (termine com Control-D).
mklement0

5
@loentar O <numbers.txté uma melhoria, mas, em geral, esta solução é eficiente apenas para pequenos arquivos de entrada; por exemplo, com um arquivo de 1.000 linhas de entrada, a awksolução aceita é cerca de 20 vezes mais rápida na minha máquina - e também consome menos memória, porque o arquivo não é lido de uma só vez.
precisa saber é o seguinte

2
Eu quase tinha perdido a esperança quando cheguei a este. Pure bash!
Omer Akhter

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

4
E os adicionei de volta: "-l" garante que a saída seja terminada por LF como `` backticks do shell e a maioria dos programas esperam, e "<" indica que este comando pode ser usado em um pipeline.
j_random_hacker

Você está certo. Como desculpa: cada personagem de uma linha do Perl exige um trabalho mental para mim; portanto, prefiro retirar o maior número possível de personagens. O hábito era prejudicial neste caso.
JFS

2
Uma das poucas soluções que não carrega tudo na RAM.
Erik Aronesty 04/10/19

28

Meus quinze centavos:

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

Exemplo:

$ 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

Minha entrada pode conter linhas em branco, então usei o que você postou aqui mais a grep -v '^$'. Obrigado!
James Oravec

Uau!! sua resposta é incrível! o meu favorito de todos na banda de rodagem
thahgr

Adoro isso e +1 para pipeline. Solução muito simples e fácil para mim
Gelin Luo 03/09

24

Fiz uma referência rápida nas respostas existentes que

  • use apenas ferramentas padrão (desculpe por coisas como luaou rocket),
  • são one-liners reais,
  • são capazes de adicionar grandes quantidades de números (100 milhões) e
  • são rápidos (eu ignorei os que levaram mais de um minuto).

Sempre adicionei os números de 1 a 100 milhões que eram factíveis na minha máquina em menos de um minuto para várias soluções.

Aqui estão os resultados:

Pitão

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

Colar & Bc

Isso ficou sem memória na minha máquina. Funcionou para metade do tamanho da entrada (50 milhões de números):

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

Então eu acho que levaria aproximadamente 35s para os 100 milhões de números.

Perl

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

Rubi

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

C

Apenas para fins de comparação, compilei a versão C e testei isso também, apenas para ter uma idéia de quanto as soluções baseadas em ferramentas são mais lentas.

#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

Conclusão

É claro que C é mais rápido com 8s, mas a solução Pypy adiciona apenas uma sobrecarga muito pequena de cerca de 30% a 11s . Mas, para ser justo, o Pypy não é exatamente padrão. A maioria das pessoas tem apenas o CPython instalado, o que é significativamente mais lento (22s), exatamente tão rápido quanto a solução Awk popular.

A solução mais rápida baseada em ferramentas padrão é o Perl (15s).


2
A abordagem paste+ bcera exatamente o que eu procurava para somar valores hexadecimais, obrigado!
Tomislav Nakic-Alfirevic

1
Apenas por diversão, em 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

resposta incrível. para não escolher, mas se você decidir incluir esses resultados de longo prazo, a resposta seria ainda mais incrível!
Steven Lu

@StevenLu Eu senti que a resposta seria mais longa e, portanto, menos impressionante (para usar suas palavras). Mas eu posso entender que este sentimento não precisa ser compartilhado por todos :)
Alfe

Próximo: numba + paralelização
gerrit 19/02

17

Forro liso da festança um

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

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

7
Não é necessário nenhum gato : echo $(( $( tr "\n" "+" < /tmp/test) 0 ))
agc

2
trnão é exatamente "plain Bash" / procurar defeitos
Benjamin W.

17

Solução BASH, se você quiser fazer deste um comando (por exemplo, se você precisar fazer isso com frequência):

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

Então uso:

addnums < /tmp/nums

14

Eu acho que AWK é o que você está procurando:

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

Você pode usar este comando passando a lista de números pela entrada padrão ou passando o arquivo que contém os números como parâmetro.



11

O seguinte funciona no bash:

I=0

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

echo $I

1
A expansão de comandos deve ser usada com cuidado quando os arquivos podem ser arbitrariamente grandes. Com numbers.txt de 10 MB, a cat numbers.txtetapa seria problemática.
Giacomo

1
De fato, porém (se não fosse pelas melhores soluções encontradas aqui), eu usaria essa até encontrar esse problema.
Francisco Canedo

11

Você pode usar num-utils, embora possa ser um exagero para o que você precisa. Este é um conjunto de programas para manipular números no shell e pode fazer várias coisas bacanas, incluindo, é claro, adicioná-las. Está um pouco desatualizado, mas eles ainda funcionam e podem ser úteis se você precisar fazer algo mais.

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


Exemplo: numsum numbers.txt.
AGC

9

Sei que essa é uma pergunta antiga, mas gosto dessa solução o suficiente para compartilhá-la.

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

Se houver interesse, explicarei como funciona.


10
Por favor não. Nós gostamos de fingir que -n e -p são coisas agradáveis semânticas, e não apenas alguns colar corda inteligente;)
hobbs

2
Sim, por favor, não explicar :) (Eu não sou um cara TypeA Perl.)
Jens

1
Tente executar "perl -MO = Deparse -lpe '$ c + = $ _} {$ _ = $ c'" e observando a saída, basicamente -l usa novas linhas e separadores de entrada e saída, e -p imprime cada linha. Mas, para executar '-p', o perl adiciona primeiro alguma placa da caldeira (que -MO = Deparse) mostrará, mas depois substitui e compila. Assim, você pode fazer com que um bloco extra seja inserido com a parte '} {' e induzi-lo a não imprimir em cada linha, mas imprimir no final.
Nym 8/07

9

Pure bash e em uma linha :-)

$ 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

Por que existem dois ((parênteses ))?
Atcold 27/10/2015

Não é realmente pura festa devido ao gato. torná-la pura festa, substituindo gato com$(< numbers.txt)
Dani_l


6

Perl puro alternativo, razoavelmente legível, sem pacotes ou opções necessárias:

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

ou um pouquinho menor: perl -e 'map {$ x + = $ _} <>; print $ x 'infile.txt
Avi Tevet

A memória necessária é de quase 2 GB para uma grande entrada de 10 milhões de números
Amit Naidu

6

Para os amantes de Ruby

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

5

Não é possível evitar o envio deste:

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

Pode ser encontrado aqui:
O linux de uma linha mais elegante do shell unix para somar uma lista de números de precisão arbitrária?

E aqui estão suas vantagens especiais sobre o awk, bc e amigos:

  • não depende de buffer e, portanto, não engasga com entradas realmente grandes
  • não implica precisão específica - ou tamanho inteiro para esse assunto - limites
  • não é necessário código diferente, se for necessário adicionar números de ponto flutuante

Inclua o código relacionado à pergunta na resposta e não se refira a um link
Ibo

5

Usando o utilitário GNU datamash :

seq 10 | datamash sum 1

Resultado:

55

Se os dados de entrada forem irregulares, com espaços e tabulações em locais ímpares, isso pode confundir datamash, então use o -Wcomutador:

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

... ou use trpara limpar o espaço em branco:

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


3

Você pode fazer isso em python, se sentir-se confortável:

Não testado, apenas digitado:

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

Sebastian apontou um roteiro único:

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

python -c "a partir da entrada FileInput importação; print sum (mapa (int, input ()))" numbers.txt
jfs

2
o gato está sendo usado em excesso, redirecione o stdin do arquivo: python -c "..." <numbers.txt
Giacomo

2
@rjack: caté usado para demonstrar que o script funciona tanto para stdin quanto para arquivos em argv [] (como while(<>)em Perl). Se sua entrada estiver em um arquivo, '<' será desnecessário.
JFS

2
Mas < numbers.txtdemonstra que ele funciona no stdin tão bem quanto cat numbers.txt |funciona. E isso não ensina maus hábitos.
Xiong Chiamiov

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

Ou, você pode digitar os números na linha de comando:

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

No entanto, este comporta o arquivo, portanto, não é uma boa ideia usá-lo em arquivos grandes. Veja a resposta de j_random_hacker, que evita o slurping.


3

O seguinte deve funcionar (supondo que seu número seja o segundo campo em cada linha).

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

2
Você realmente não precisa da parte {sum = 0}
Uphill_ What '1/11/11

3

Uma linha na raquete:

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

3

C (não simplificado)

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)

Eu tive que aprovar o comentário. Não há nada errado com a resposta - é muito bom. No entanto, para mostrar que o comentário torna a resposta incrível, eu estou apenas votando no comentário.
bballdave025

3

Pedimos desculpas antecipadamente pela legibilidade dos backticks ("` "), mas eles funcionam em conchas que não sejam o bash e, portanto, são mais fáceis de colar. Se você usa um shell que o aceita, o formato $ (command ...) é muito mais legível (e, portanto, depurável) do que o `command ...` então sinta-se à vontade para modificar sua sanidade.

Eu tenho uma função simples no meu bashrc que usará o awk para calcular vários itens simples de matemática

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

Isso fará +, -, *, /, ^,%, sqrt, sin, cos, parênteses .... (e mais, dependendo da sua versão do awk) ... você pode até gostar do ponto flutuante printf e format saída, mas isso é tudo que eu normalmente preciso

para esta pergunta em particular, eu simplesmente faria isso para cada linha:

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

então o bloco de código para somar cada linha ficaria assim:

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

Isto é, se você quiser somar apenas linha por linha. No entanto, para um total de cada número no arquivo de dados

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

btw se eu precisar fazer algo rápido na área de trabalho, eu uso o seguinte:

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

2
Que tipo de concha antiga você está usando que não suporta $()?
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.