逐行相加整数的Shell命令?

1083

我正在寻找一个命令,它将接受多行文本作为输入,每行包含一个整数,并输出这些整数的总和。

背景说明:我有一个日志文件,其中包括时间测量。通过grep相关行和一些sed重新格式化,我可以列出该文件中的所有计时。我想计算出总时间。我可以将此中间输出传输到任何命令以进行最终求和。我过去总是使用expr,但是除非它以RPN模式运行,否则我认为它无法处理这个问题(即使是在RPN模式下也很棘手)。

如何获得整数的总和?


2
这与我之前问过的一个问题非常相似:https://dev59.com/OHVC5IYBdhLWcg3wbglT - An̲̳̳drew
这个问题感觉像是一个代码高尔夫的问题。https://codegolf.stackexchange.com/ :) - Gordon Bean
47个回答

36

我对现有答案进行了快速基准测试,这些答案:

  • 仅使用标准工具(抱歉,不包括像luarocket之类的东西)
  • 是真正的一行代码
  • 能够添加大量数字(1亿个)
  • 速度快(我忽略了需要超过一分钟的答案)

我一直在把1到1亿的数字相加,对于几种解决方案,在我的计算机上在不到一分钟的时间内就可以完成。

以下是结果:

Python

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

粘贴和BC

在我的电脑上,这个程序因内存不足而崩溃。它只能处理一半大小的输入(5000万个数字):

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

所以我猜测生成1亿个数字需要大约35秒时间。

Perl

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

Ruby

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

C

出于比较的目的,我还编译了 C 版本并进行了测试,只是为了了解基于工具的解决方案有多慢。

#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

结论

C语言是最快的,只需8秒,但Pypy解决方案仅增加了约30%的额外开销,达到了11秒。但是,公平地说,Pypy并不完全标准化。大多数人只安装了CPython,其速度慢得多(22秒),与流行的Awk解决方案一样快。

基于标准工具的最快解决方案是Perl(15秒)。


2
“paste” + “bc” 的方法正是我寻找的用于求和十六进制值的,谢谢! - Tomislav Nakic-Alfirevic
1
仅供娱乐,使用 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::().unwrap(); } println!("{}", sum); } - Jocelyn
@StevenLu 我觉得答案只会变得更加“冗长”,因此也就不那么“棒了”(用你的话来说)。但我可以理解并不是每个人都有这种感觉 :) - Alfe
1
接下来:numba + 并行化 - gerrit
如果你使用了$0而不是$1,awk也可以匹配perl。 - Amit Naidu
显示剩余3条评论

29

使用GNU datamash 工具:

seq 10 | datamash sum 1

输出:

55
如果输入数据不规则,有空格和制表符在奇怪的位置,这可能会使datamash混淆,那么可以使用-W开关:
<commands...> | datamash -W sum 1

...或者使用tr来清除空格:

<commands...> | tr -d '[[:blank:]]' | datamash sum 1
如果输入足够大,输出将会使用科学计数法。
seq 100000000 | datamash sum 1

输出:

5.00000005e+15

要将它转换为十进制,请使用 --format 选项:

seq 100000000 | datamash  --format '%.0f' sum 1

输出:

5000000050000000

这在我的 Github CLI 分页计数中非常有效 <3 - Sridhar Sarnobat
限制:datamash只能处理流,不能处理文件,因此它只会使用一个CPU核心,没有并行化。 - undefined

20

简单的Bash一行代码

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

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

10
дёҚйңҖиҰҒвҖңзҢ«вҖқзҡ„е‘Ҫд»Өпјҡ echo $(( $( tr "\n" "+" < /tmp/test) 0 )) пјҲжіЁпјҡиҝҷжҳҜдёҖдёӘLinuxе‘Ҫд»ӨпјҢз”ЁдәҺе°Ҷж–Ү件дёӯжҜҸиЎҢзҡ„ж•°еӯ—зӣёеҠ е№¶иҫ“еҮәз»“жһңгҖӮпјү - agc
8
tr 并不完全是“纯粹的 Bash”/吹毛求疵 - Benjamin W.

20

BASH的解决方案,如果您想将此作为命令使用(例如,如果您需要频繁执行此操作):

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

然后的用法:

addnums < /tmp/nums

17

你可以使用num-utils,虽然它可能会过于强大,超出你的需要范畴。这是一组用于在shell中操作数字的程序,并且可以执行几个聪明的操作,包括当然是对它们进行加法运算。这个程序有点过时了,但它们仍然有效,如果你需要做更多的事情,它们可能会很有用。

https://suso.suso.org/programs/num-utils/index.phtml

它非常简单易用:

$ seq 10 | numsum
55

但是对于大输入会耗尽内存。

$ seq 100000000 | numsum
Terminado (killed)

示例:“numsum numbers.txt”。 - agc
使用管道的示例:printf "%s\n" 1 3 5 | numsum - Jan Matějka

14

无法避免提交此内容,这是对该问题最常见的方法,请查看:

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

这里可以找到它,我是 OP,答案来自观众:

以下是它相对于 awkbcperlGNU 的 datamash 等的特殊优势:

  • 它使用标准实用程序,在任何Unix环境中都很常见。
  • 它不依赖缓冲区,因此不会因为输入太长而受阻。
  • 它不暗示任何特定的精度限制 - 或整数大小,你好 AWK 朋友们!
  • 如果需要添加浮点数,则无需不同的代码。
  • 在最小的环境中理论上可以运行顺畅。

请在答案中包含与问题相关的代码,不要引用链接。 - Ibo
1
它也恰巧比所有其他解决方案慢得多,比datamash解决方案慢10倍以上。 - Gabriel Ravier
@GabrielRavier OP没有将速度定义为首要要求,因此在缺乏这方面的情况下,通用的工作解决方案会更受欢迎。顺便说一下,datamash在所有Unix平台上都不是标准的,例如MacOSX似乎缺少它。 - fgeorgatos
@fgeorgatos 这是真的,但我只是想指出给其他看到这个问题的人,实际上,与大多数Linux系统上可以获得的速度相比,这个答案非常慢。 - Gabriel Ravier
@GabrielRavier,你能提供一些可比较的测量数字吗?顺便说一下,我已经运行了几个jot测试,即使对于相当大的列表,速度也非常合理。另外,如果将datamash作为OP问题的解决方案,则任何编译的汇编程序也应该是可以接受的...这将加快速度! - fgeorgatos
1
@fgeorgatos 经过一些计算,我可以确认它比原来快了10倍以上。time seq 10000000 | sed '2,$s/$/+/;$s/$/p/' | dc 计算结果正确,用时43秒,而 time seq 10000000 | datamash sum 1 只需1秒钟即可完成,速度超过40倍。此外,正如您所说的“编译汇编程序”,可能是一个更加复杂的解决方案,速度也不会更快,并且更有可能给出错误的解决方案。 - Gabriel Ravier

12

我知道这是一个老问题,但我喜欢这个解决方案足够分享它。

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

如果有兴趣,我会解释它的工作原理。


11
请不要这样做。我们喜欢把-n和-p看作是一些好的语义东西,而不仅仅是一些巧妙的字符串拼接 ;) - hobbs
2
好的,请解释一下 :) (我不是 Perl 类型的人。) - Jens
4
尝试运行命令"perl -MO=Deparse -lpe '$c+=$}{$=$c'" 并查看输出结果。基本上,选项"-l"使用换行符以及输入和输出分隔符,而"-p"打印每一行。但为了使用"-p",perl首先会添加一些样板代码(使用"-MO=Deparse"可以显示),然后再进行替换和编译。因此,您可以通过在代码中添加"}{"部分来欺骗perl插入额外的代码块,并使其不在每行打印,而是在最后打印。 - Nym

11
以下内容适用于bash:
I=0

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

echo $I

1
当文件可能会任意大时,应谨慎使用命令扩展。 对于大小为10MB的numbers.txt文件,cat numbers.txt 步骤将会有问题。 - Giacomo
1
事实上,如果没有在这里找到更好的解决方案,我将使用这个直到我真正遇到那个问题。 - Francisco Canedo

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

8
纯bash编写,只用一行代码就能搞定 :-)
$ 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

为什么有两个 (( 括号 )) - Atcold
1
由于使用了cat命令,它并不是真正的纯bash。通过将cat替换为$(< numbers.txt),使其成为纯bash。 - Dani_l

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接