关于linux:如何快速汇总文件中的所有数字?

How can I quickly sum all numbers in a file?

我有一个包含几千个数字的文件,每个数字都在自己的行中:

1
2
3
4
5
6
7
34
42
11
6
2
99
...

我正在写一个脚本,它将打印文件中所有数字的总和。我有解决办法,但效率不高。(运行需要几分钟)我正在寻找更有效的解决方案。有什么建议吗?


你可以用锥子:

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


对于一个PerlOne线性,它基本上与Ayman Hourieh的答案中的awk解决方案相同:

1
 % perl -nle '$sum += $_ } END { print $sum'

如果您想知道PerlOne的行程序是做什么的,可以将它们放在下面:

1
 %  perl -MO=Deparse -nle '$sum += $_ } END { print $sum'

结果是程序的一个更详细的版本,形式是没有人会自己写:

1
2
3
4
5
6
7
8
9
10
11
BEGIN { $/ ="
"
; $\ ="
"
; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    $sum += $_;
}
sub END {
    print $sum;
}
-e syntax OK

只是为了咯咯笑,我用一个包含1000000个数字的文件(在0-9999范围内)尝试了这个方法。在我的Mac Pro上,它几乎瞬间返回。这太糟糕了,因为我希望使用mmap会非常快,但同时:

1
2
3
4
5
6
7
8
use 5.010;
use File::Map qw(map_file);

map_file my $map, $ARGV[0];

$sum += $1 while $map =~ m/(\d+)/g;

say $sum;


到目前为止,没有一种解决方案使用paste。这里有一个:

1
paste -sd+ filename | bc

例如,计算∑n,其中1<=n<=100000:

1
2
$ seq 100000 | paste -sd+ | bc -l
5000050000

(出于好奇,seq n会打印一个从1n的数字序列,给定一个正数n。)


为了好玩,让我们来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
$ for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers

$ time perl -nle '$sum += $_ } END { print $sum' random_numbers
16379866392

real    0m0.226s
user    0m0.219s
sys     0m0.002s

$ time awk '{ sum += $1 } END { print sum }' random_numbers
16379866392

real    0m0.311s
user    0m0.304s
sys     0m0.005s

$ time { { tr"
"
+ < random_numbers ; echo 0; } | bc; }
16379866392

real    0m0.445s
user    0m0.438s
sys     0m0.024s

$ time { s=0;while read l; do s=$((s+$l));done<random_numbers;echo $s; }
16379866392

real    0m9.309s
user    0m8.404s
sys     0m0.887s

$ time { s=0;while read l; do ((s+=l));done<random_numbers;echo $s; }
16379866392

real    0m7.191s
user    0m6.402s
sys     0m0.776s

$ time { sed ':a;N;s/
/+/;ta'
random_numbers|bc; }
^C

real    4m53.413s
user    4m52.584s
sys 0m0.052s

我在5分钟后中止了SED的运行

我一直潜到卢亚,而且速度很快:

1
2
3
4
5
6
$ time lua -e 'sum=0; for line in io.lines() do sum=sum+line end; print(sum)' < random_numbers
16388542582.0

real    0m0.362s
user    0m0.313s
sys     0m0.063s

当我更新这个的时候,Ruby:

1
2
3
4
5
6
$ time ruby -e 'sum = 0; File.foreach(ARGV.shift) {|line| sum+=line.to_i}; puts sum' random_numbers
16388542582

real    0m0.378s
user    0m0.297s
sys     0m0.078s

听从艾德莫顿的建议:使用$1

1
2
3
4
5
6
$ time awk '{ sum += $1 } END { print sum }' random_numbers
16388542582

real    0m0.421s
user    0m0.359s
sys     0m0.063s

vs使用$0

1
2
3
4
5
6
$ time awk '{ sum += $0 } END { print sum }' random_numbers
16388542582

real    0m0.302s
user    0m0.234s
sys     0m0.063s


这工作:

1
2
{ tr '
'
+; echo 0; } < file.txt | bc


另一种选择是使用jq

1
2
$ seq 10|jq -s add
55

-s(--slurp)将输入行读入一个数组。


这是一个直接的重击:

1
2
3
4
5
6
sum=0
while read -r line
do
    (( sum += line ))
done < file
echo $sum


这是另一条单线

1
( echo 0 ; sed 's/$/ +/' foo ; echo p ) | dc

这假设数字是整数。如果您需要小数,请尝试

1
( echo 0 2k ; sed 's/$/ +/' foo ; echo p ) | dc

将2调整为所需的小数位数。


我更喜欢将GNU数据mash用于此类任务,因为它比Perl或Awk更简洁易读。例如

1
datamash sum 1 < myfile

其中1表示数据的第一列。


1
cat nums | perl -ne '$sum += $_ } { print $sum'

(与Brian D Foy的回答相同,没有‘结束’)


为了好玩,让我们用pdl,Perl的数组数学引擎来做吧!

1
perl -MPDL -E 'say rcols(shift)->sum' datafile

rcols将列读入矩阵(本例中为1d),sum将矩阵的所有元素相加。


下面是一个使用带有生成器表达式的Python的解决方案。在我的旧电脑上测试了一百万个数字。

1
2
3
4
5
time python -c"import sys; print sum((float(l) for l in sys.stdin))" < file

real    0m0.619s
user    0m0.512s
sys     0m0.028s


我更喜欢用r来表示:

1
$ R -e 'sum(scan("filename"))'

1
$ perl -MList::Util=sum -le 'print sum <>' nums.txt


1
2
sed ':a;N;s/
/+/;ta'
file|bc

更简洁:

1
2
3
4
5
# Ruby
ruby -e 'puts open("random_numbers").map(&:to_i).reduce(:+)'

# Python
python -c 'print(sum(int(l) for l in open("random_numbers")))'

Perl & Nbsp;6

1
say sum lines
1
2
3
4
~$ perl6 -e '.say for 0..1000000' > test.in

~$ perl6 -e 'say sum lines' < test.in
500000500000

与露比:

1
ruby -e"File.read('file.txt').split.inject(0){|mem, obj| mem += obj.to_f}"


另一个娱乐

1
sum=0;for i in $(cat file);do sum=$((sum+$i));done;echo $sum

或者只是一次狂欢

1
s=0;while read l; do s=$((s+$l));done<file;echo $s

但awk解决方案可能是最好的,因为它是最紧凑的。


我没有测试过这个,但是它应该可以工作:

1
2
3
cat f | tr"
"
"+" | sed 's/+$/
/'
| bc

如果BC不处理EOF和EOL,您可能需要在BC之前向字符串添加""(如通过echo)。


考虑到你需要通读整个文件,我不知道你是否能比这更好。

1
2
3
4
5
$sum = 0;
while(<>){
   $sum += $_;
}
print $sum;


您可以使用alacon命令行实用程序对alasql数据库执行此操作。

它与node.js一起工作,因此需要先安装node.js,然后安装alasql包:

要从txt文件计算总和,可以使用以下命令:

1
> node alacon"SELECT VALUE SUM([0]) FROM TXT('mydata.txt')"

这里还有一个:

1
2
3
4
5
6
7
8
9
open(FIL,"a.txt");

my $sum = 0;
foreach( <FIL> ) {chomp; $sum += $_;}

close(FIL);

print"Sum = $sum
"
;

+替换所有新行,增加0并发送给Ruby解释器并不容易?

1
(sed -e"s/$/+/" file; echo 0)|irb

如果没有irb,可以发送到bc,但是除了最后一条(echo)之外,必须删除所有新行。最好是用tr来做这个,除非你有sed的博士学位。

1
2
(sed -e"s/$/+/" file|tr -d"
"
; echo 0)|bc


C总是以速度取胜:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    ssize_t read;
    char *line = NULL;
    size_t len = 0;
    double sum = 0.0;

    while (read = getline(&line, &len, stdin) != -1) {
        sum += atof(line);
    }

    printf("%f", sum);
    return 0;
}

1米数字的计时(与我的python答案相同的机器/输入):

1
2
3
4
5
$ gcc sum.c -o sum && time ./sum < numbers
5003371677.000000
real    0m0.188s
user    0m0.180s
sys     0m0.000s


cat f | tr"
""+" | perl -pne chop | R --vanilla --slave


可笑的是:

1
2
cat f | tr"
"
"+" | perl -pne chop | R --vanilla --slave