Pregunta Comando de shell para sumar enteros, uno por línea?


Estoy buscando un comando que acepte como entrada múltiples líneas de texto, cada línea contiene un solo entero, y muestra la suma de estos enteros.

Como un poco de fondo, tengo un archivo de registro que incluye mediciones de tiempo, por lo que a través de grepping para las líneas pertinentes, y un poco de sed reformateo Puedo enumerar todos los tiempos en ese archivo. Me gustaría calcular el total sin embargo, y mi mente se ha quedado en blanco en cuanto a cualquier comando que pueda canalizar esta salida intermedia para hacer la suma final. Siempre he usado expr en el pasado, pero a menos que se ejecute en RPN mode No creo que vaya a hacer frente a esto (y aun así sería complicado).

¿Qué me estoy perdiendo? Dado que probablemente haya varias maneras de lograr esto, estaré encantado de leer (y upvote) cualquier enfoque que funcione, incluso si alguien más ya ha publicado una solución diferente que hace el trabajo.

Pregunta relacionada: ¿El comando más corto para calcular la suma de una columna de salida en Unix?  (créditos @Andrés)


Actualizar: Guau, como es de esperar, hay algunas buenas respuestas aquí. Parece que definitivamente tendré que dar awk inspección más profunda como una command-line tool ¡en general!


683


origen


Respuestas:


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 superior a 2 ^ 31 (2147483647). Ver comentarios para más antecedentes. Una sugerencia es usar printf más bien que print:

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

1084



Normalmente, Pegar 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 de delimitador le permite pasar una ecuación de tipo x + x a bc.

paste -s -d+ infile | bc

Alternativamente, cuando la tubería de stdin,

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

571



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

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

101



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

68



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

Tenga en cuenta que los números negativos con prefijo con el signo menos se deben traducir por dc, ya que usa _ prefijo en lugar de - prefijo para eso. Por ejemplo, a través de tr '-' '_' | dc -f- -e '...'.

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

La expresion [+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 pseudo-código:

  1. Defina "add_top_of_stack" como:
    1. Elimina los dos valores superiores de la pila y agrega el resultado
    2. Si la pila tiene dos o más valores, ejecuta "add_top_of_stack" recursivamente
  2. Si la pila tiene dos o más valores, ejecuta "add_top_of_stack"
  3. Imprima el resultado, ahora es el único elemento que queda en la pila

Para comprender realmente la simplicidad y el poder de dc, aquí hay una secuencia de comandos de Python en funcionamiento que implementa algunos de los comandos de dc y 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()

60



Pondré una gran ADVERTENCIA sobre la solución comúnmente aprobada:

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

esto es porque en esta forma awk usa una representación de entero de 32 bits con signo: se desbordará por sumas que exceden 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

PD Me hubiera gustado comentar la primera respuesta, pero no tengo suficiente reputación ...


60



Bash puro y corto.

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

40



Con jq:

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

40



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

36



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

24