¿Comando Shell para sumar enteros, uno por línea?


867

Estoy buscando un comando que acepte (como entrada) varias líneas de texto, cada línea que contenga un solo entero, y genere la suma de estos enteros.

Como fondo, tengo un archivo de registro que incluye mediciones de tiempo. A través del grepping para las líneas relevantes y un poco de sedformateo, puedo enumerar todos los tiempos en ese archivo. Me gustaría calcular el total. Puedo canalizar esta salida intermedia a cualquier comando para hacer la suma final. Siempre lo he usado expren el pasado, pero a menos que se ejecute en modo RPN, no creo que vaya a hacer frente a esto (e incluso entonces sería complicado).

¿Cómo puedo obtener la suma de enteros?


2
Esto es muy similar a una pregunta que hice hace un tiempo: stackoverflow.com/questions/295781/…
An̲̳̳drew

55
Realmente me gusta esta pregunta por el hecho de que hay muchas posibles respuestas correctas (o al menos que funcionan).
Francisco Canedo el

Esta pregunta se siente como un problema para el golf de código. codegolf.stackexchange.com :)
Gordon Bean

Respuestas:


1322

¿Un poco de awk debería hacerlo?

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

Nota: algunas versiones de awk tienen comportamientos extraños si va a agregar algo que exceda 2 ^ 31 (2147483647). Ver comentarios para más antecedentes. Una sugerencia es usar en printflugar de print:

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

77
¡Hay mucho amor horrible en esta habitación! Me gusta como un simple script como este podría ser modificado para añadir una segunda columna de datos con sólo cambiar el $ 1 a $ 2
Paul Dixon

2
No hay un límite práctico, ya que procesará la entrada como una secuencia. Entonces, si puede manejar un archivo de líneas X, puede estar bastante seguro de que puede manejar X + 1.
Paul Dixon

44
Una vez escribí un procesador rudimentario de listas de correo con un script awk ejecutado a través de la utilidad de vacaciones. Buenos tiempos. :)
LS

2
acabo de usar esto para: guión de páginas de conteo de todos los documentos:ls $@ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk '{s+=$2} END {print s}'
ovejas voladoras

8
Tenga cuidado, no funcionará con números mayores que 2147483647 (es decir, 2 ^ 31), porque awk usa una representación entera con signo de 32 bits. Usar en su awk '{s+=$1} END {printf "%.0f", s}' mydatafilelugar.
Giancarlo Sportelli

665

Pegar normalmente combina líneas de varios archivos, pero también se puede usar para convertir líneas individuales de un archivo en una sola línea. El indicador delimitador le permite pasar una ecuación de tipo x + x a bc.

paste -s -d+ infile | bc

Alternativamente, al conectar desde stdin,

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

1
¡Muy agradable! Hubiera puesto un espacio antes del "+", solo para ayudarme a analizarlo mejor, pero eso fue muy útil para canalizar algunos números de memoria a través de pegar y luego bc.
Michael H.

73
Mucho más fácil de recordar y escribir que la solución awk. Además, tenga en cuenta que pastepuede usar un guión -como nombre de archivo, lo que le permitirá canalizar los números de la salida de un comando a la salida estándar de pegar sin la necesidad de crear primero un archivo:<commands> | paste -sd+ - | bc
George

19
Tengo un archivo con 100 millones de números. El comando awk toma 21s; el comando pegar toma 41s. Pero bueno para cumplir con 'pegar' sin embargo!
Abhi el

44
@Abhi: Interesante: Supongo que me tomaría 20 años descifrar el comando awk, de modo que se iguala hasta que pruebe 100 millones y uno: D
Mark K Cowan

66
@George Sin embargo, puedes dejar de lado el -. (Es útil si desea combinar un archivo con stdin).
Alois Mahdal

128

La versión de una línea en Python:

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

One-liner anterior no funciona para archivos en sys.argv [], pero ese sí stackoverflow.com/questions/450799/…
jfs

Es cierto: el autor dijo que iba a canalizar la salida de otro script al comando y que estaba tratando de hacerlo lo más breve posible :)
dF.

39
La versión más corta seríapython -c"import sys; print(sum(map(int, sys.stdin)))"
jfs

44
Me encanta esta respuesta por su facilidad de lectura y flexibilidad. Necesitaba el tamaño promedio de archivos de menos de 10Mb en una colección de directorios y lo modifiqué para esto: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
También puede filtrar números que no son si tiene algún texto mezclado:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))
Granitosaurus

91

Pondría una gran ADVERTENCIA en la solución comúnmente aprobada:

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

eso es porque en esta forma awk usa una representación entera con signo de 32 bits: se desbordará para sumas que excedan 2147483647 (es decir, 2 ^ 31).

Una respuesta más general (para sumar enteros) sería:

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

¿Por qué ayuda printf () aquí? El desbordamiento de int habrá sucedido antes porque el código de suma es el mismo.
Robert Klemme

99
Porque el problema está realmente en la función "imprimir". Awk usa números enteros de 64 bits, pero por alguna razón print donws los escala a 32 bits.
Giancarlo Sportelli

44
El error de impresión parece estar solucionado, al menos para awk 4.0.1 y bash 4.3.11, a menos que me equivoque: echo -e "2147483647 \n 100" |awk '{s+=$1}END{print s}'muestra2147483747
Xen2050

44
El uso de flotadores solo introduce un nuevo problema: echo 999999999999999999 | awk '{s+=$1} END {printf "%.0f\n", s}'produce1000000000000000000
Patrick

1
¿No debería simplemente usar "% ld" en sistemas de 64 bits para que no se trunque printf a 32 bits? Como señala @Patrick, las carrozas no son una gran idea aquí.
yerforkferchips


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

Tenga en cuenta que los números negativos con el prefijo menos deben traducirse dc, ya que utiliza _prefijo en lugar de -prefijo para eso. Por ejemplo, vía tr '-' '_' | dc -f- -e '...'.

Editar: Dado que esta respuesta obtuvo tantos votos "por oscuridad", aquí hay una explicación detallada:

La expresión [+z1<r]srz1<rp hace lo siguiente :

[   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 seudocódigo:

  1. Defina "add_top_of_stack" como:
    1. Elimine los dos valores superiores de la pila y agregue el resultado nuevamente
    2. Si la pila tiene dos o más valores, ejecute "add_top_of_stack" de forma recursiva
  2. Si la pila tiene dos o más valores, ejecute "add_top_of_stack"
  3. Imprima el resultado, ahora el único elemento que queda en la pila

Para comprender realmente la simplicidad y el poder de dc, aquí hay un script de Python en funcionamiento que implementa algunos de los comandos dcy ejecuta una versión de Python del comando anterior:

### 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 es solo la herramienta de elección para usar. Pero lo haría con un poco menos de operaciones de pila. Asumieron que todas las líneas realmente contienen un número: (echo "0"; sed 's/$/ +/' inp; echo 'pq')|dc.
ikrabbe

55
El algoritmo en línea: dc -e '0 0 [+?z1<m]dsmxp'. Por lo tanto, no guardamos todos los números en la pila antes de procesarlos, sino que los leemos y procesamos uno por uno (para ser más precisos, línea por línea, ya que una línea puede contener varios números). Tenga en cuenta que la línea vacía puede terminar una secuencia de entrada.
ruvim

@ikrabbe eso es genial. En realidad, puede acortarse con un carácter más: el espacio en la sedsustitución se puede eliminar, ya que dc no le importan los espacios entre argumentos y operadores. (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)'

77
Me gusta esto porque supongo que es tan claro y corto que en realidad podría recordarlo.
Alfe

46

Golpe puro y corto.

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

99
Esta es la mejor solución porque no crea ningún subproceso si reemplaza la primera línea con f=$(<numbers.txt).
Loentar

1
alguna forma de tener la entrada de stdin? como de una pipa?
njzk2

@ njzk2 Si ingresa f=$(cat); echo $(( ${f//$'\n'/+} ))un script, puede canalizar cualquier cosa a ese script o invocarlo sin argumentos para la entrada stdin interactiva (terminar con Control-D).
mklement0

55
@loentar The <numbers.txtes una mejora, pero, en general, esta solución solo es eficiente para archivos de entrada pequeños; por ejemplo, con un archivo de 1,000 líneas de entrada, la awksolución aceptada es aproximadamente 20 veces más rápida en mi máquina, y también consume menos memoria, porque el archivo no se lee todo de una vez.
mklement0

2
Casi había perdido la esperanza cuando llegué a este. Puro bash!
Omer Akhter

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

44
Y los agregué de nuevo: "-l" asegura que la salida se termina en LF como `` backticks de shell '' y la mayoría de los programas esperan, y "<" indica que este comando se puede usar en una tubería.
j_random_hacker

Tienes razón. Como excusa: cada personaje en Perl one-liners requiere un trabajo mental para mí, por lo tanto, prefiero despojar tantos personajes como sea posible. El hábito era dañino en este caso.
jfs el

2
Una de las pocas soluciones que no carga todo en la RAM.
Erik Aronesty

28

Mis quince centavos:

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

Ejemplo:

$ 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

Mi entrada podría contener líneas en blanco, así que usé lo que publicaste aquí más a grep -v '^$'. ¡Gracias!
James Oravec

¡¡Guau!! tu respuesta es asombrosa! mi favorito personal de todos en la banda de rodadura
thahgr

Me encanta esto y +1 para la tubería. Solución muy simple y fácil para mí
Gelin Luo

24

He hecho un punto de referencia rápido sobre las respuestas existentes que

  • use solo herramientas estándar (perdón por cosas como luao rocket),
  • son frases reales,
  • son capaces de agregar grandes cantidades de números (100 millones), y
  • son rápidos (ignoré los que tomaron más de un minuto).

Siempre agregué los números de 1 a 100 millones que era factible en mi máquina en menos de un minuto para varias soluciones.

Aquí están los resultados:

Pitón

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

Pasta y Bc

Esto se quedó sin memoria en mi máquina. Funcionó por la mitad del tamaño de la entrada (50 millones de números):

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

Entonces supongo que habría tomado ~ 35s para los 100 millones 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

Rubí

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

C

Solo por el bien de la comparación, compilé la versión C y probé esto también, solo para tener una idea de cuánto más lentas son las soluciones basadas en herramientas.

#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

Conclusión

C es, por supuesto, el más rápido con 8s, pero la solución Pypy solo agrega una sobrecarga muy pequeña de aproximadamente 30% a 11s . Pero, para ser justos, Pypy no es exactamente estándar. La mayoría de las personas solo tienen instalado CPython, que es significativamente más lento (22 s), exactamente tan rápido como la popular solución Awk.

La solución más rápida basada en herramientas estándar es Perl (15s).


2
El enfoque paste+ bcera justo lo que estaba buscando para sumar valores hexadecimales, ¡gracias!
Tomislav Nakic-Alfirevic

1
Solo por diversión, en 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

respuesta asombrosa no es una trampa, pero es el caso de que si decidiera incluir esos resultados a largo plazo, ¡la respuesta sería aún más increíble!
Steven Lu

@StevenLu Sentí que la respuesta sería más larga y, por lo tanto, menos sorprendente (para usar tus palabras). Pero puedo entender que este sentimiento no necesita ser compartido por todos :)
Alfe

Siguiente: numba + paralelización
gerrit

17

Plain bash one liner

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

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

77
No se necesita gato : echo $(( $( tr "\n" "+" < /tmp/test) 0 ))
agc

2
trno es exactamente "simple Bash" / nitpick
Benjamin W.

17

Solución BASH, si desea hacer esto un comando (por ejemplo, si necesita hacer esto con frecuencia):

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

Entonces uso:

addnums < /tmp/nums

14

Creo que AWK es lo que estás buscando:

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

Puede usar este comando ya sea pasando la lista de números a través de la entrada estándar o pasando el archivo que contiene los números como parámetro.



11

Lo siguiente funciona en bash:

I=0

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

echo $I

1
La expansión de comandos debe usarse con precaución cuando los archivos pueden ser arbitrariamente grandes. Con números.txt de 10MB, el cat numbers.txtpaso sería problemático.
Giacomo

1
De hecho, sin embargo (si no fuera por las mejores soluciones que se encuentran aquí) usaría esta hasta que realmente encuentre ese problema.
Francisco Canedo el

11

Puede usar num-utils, aunque puede ser excesivo para lo que necesita. Este es un conjunto de programas para manipular números en el shell, y puede hacer varias cosas ingeniosas, incluso, por supuesto, sumarlas. Está un poco desactualizado, pero aún funcionan y pueden ser útiles si necesita hacer algo más.

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


Ejemplo: numsum numbers.txt.
agc

9

Me doy cuenta de que esta es una vieja pregunta, pero me gusta esta solución lo suficiente como para compartirla.

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

Si hay interés, explicaré cómo funciona.


10
Por favor no lo hagas. Nos gusta pretender que -n y -p son cosas semánticas agradables, no solo un ingenioso pegado de cadenas;)
hobbs

2
Sí, por favor, explique :) (No soy un tipo Perl typea.)
Jens

1
Intente ejecutar "perl -MO = Deparse -lpe '$ c + = $ _} {$ _ = $ c'" y mirando la salida, básicamente -l usa líneas nuevas y separadores de entrada y salida, y -p imprime cada línea. Pero para hacer '-p', perl primero agrega un poco de placa de caldera (que -MO = Deparse) le mostrará, pero luego solo sustituye y compila. Por lo tanto, puede hacer que se inserte un bloque adicional con la parte '} {' y engañarlo para que no se imprima en cada línea, sino que se imprima al final.
Nym

9

Golpe puro y de una sola línea :-)

$ 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 qué hay dos ((paréntesis ))?
Atcold

No es realmente puro golpe debido al gato. que sea puro golpe reemplazando gato con$(< numbers.txt)
Dani_l


6

Perl puro alternativo, bastante legible, no se requieren paquetes u opciones:

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

o un poco más corto: perl -e 'map {$ x + = $ _} <>; print $ x 'infile.txt
Avi Tevet

La memoria requerida es de casi 2 GB para una gran entrada de 10 millones de números
Amit Naidu

6

Para los amantes del rubí

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

5

No puedo evitar enviar esto:

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

Se encuentra aquí: ¿
La línea elegante de caparazón Unix más elegante para sumar la lista de números de precisión arbitraria?

Y aquí están sus ventajas especiales sobre awk, bc y amigos:

  • no depende del almacenamiento en búfer y, por lo tanto, no se ahoga con entradas realmente grandes
  • no implica una precisión particular -o tamaño entero para esa materia- límites
  • no es necesario un código diferente, si es necesario agregar números de coma flotante

Incluya el código relacionado con la pregunta en la respuesta y no haga referencia a un enlace
Ibo

5

Usando la utilidad GNU datamash :

seq 10 | datamash sum 1

Salida:

55

Si los datos de entrada son irregulares, con espacios y pestañas en lugares impares, esto puede confundir datamash, entonces use el -Winterruptor:

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

... o use trpara limpiar el espacio en blanco:

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


3

Puedes hacerlo en python, si te sientes cómodo:

No probado, solo escrito:

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

Sebastian señaló un guión de una línea:

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

python -c "desde la entrada de importación de entrada de archivo; print sum (map (int, input ()))"
numbers.txt

2
El gato está sobreutilizado, redirige el stdin del archivo: python -c "..." <numbers.txt
Giacomo

2
@rjack: catse utiliza para demostrar que el script funciona tanto para stdin como para archivos en argv [] (como while(<>)en Perl). Si su entrada está en un archivo, entonces '<' es innecesario.
jfs el

2
Pero < numbers.txtdemuestra que funciona en stdin tan bien como lo cat numbers.txt |hace. Y no enseña malos hábitos.
Xiong Chiamiov

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

O bien, puede escribir los números en la línea de comando:

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

Sin embargo, este extrae el archivo, por lo que no es una buena idea usarlo en archivos grandes. Vea la respuesta de j_random_hacker que evita sorber.


3

Lo siguiente debería funcionar (suponiendo que su número sea el segundo campo en cada línea).

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

2
Realmente no necesitas la parte {sum = 0}
Uphill_ What '1

3

One-liner en raqueta:

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

3

C (no 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)

Tuve que votar el comentario. La respuesta no tiene nada de malo, es bastante buena. Sin embargo, para mostrar que el comentario hace que la respuesta sea increíble, solo estoy votando el comentario.
bballdave025

3

Disculpas de antemano por la legibilidad de los backticks ("` "), pero estos funcionan en shells que no sean bash y, por lo tanto, son más pegajosos. Si usa un shell que lo acepta, el formato $ (comando ...) es mucho más legible (y, por lo tanto, depurable) que 'comando ...', así que siéntase libre de modificarlo por su cordura.

Tengo una función simple en mi bashrc que usará awk para calcular una cantidad de elementos matemáticos simples

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

Esto hará +, -, *, /, ^,%, sqrt, sin, cos, paréntesis ... (y más, dependiendo de su versión de awk) ... incluso podría imaginarse con printf y formatear el punto flotante salida, pero esto es todo lo que normalmente necesito

para esta pregunta en particular, simplemente haría esto para cada línea:

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

entonces el bloque de código para sumar cada línea se vería así:

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

Eso si quisieras sumar solo línea por línea. Sin embargo, para un total de cada número en el archivo de datos

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

por cierto, si necesito hacer algo rápido en el escritorio, uso esto:

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

2
¿Qué tipo de caparazón antiguo estás usando que no es compatible $()?
nyuszika7h
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.