Shell命令对整数求和,每行一个?


866

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

作为背景知识,我有一个包含时序测量的日志文件。通过grepping相关行和sed重新格式化,我可以列出该文件中的所有时间。我想算出总数。我可以将此中间输出通过管道传递给任何命令,以进行最终求和。我过去一直使用expr过,但是除非它以RPN模式运行,否则我认为它不能解决这个问题(即使那样也很棘手)。

如何获得整数的总和?


2
这非常类似于我之前问过的一个问题:stackoverflow.com/questions/295781/…–
An̲̳̳drew

5
我真的很喜欢这个问题,因为有很多可能的正确(或至少有效的)答案。
Francisco Canedo

这个问题感觉像是代码高尔夫的问题。codegolf.stackexchange.com :)
Gordon Bean

Answers:


1321

应该做一点awk吗?

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

注意:如果要添加超过2 ^ 31(2147483647)的任何内容,某些版本的awk会有一些奇怪的行为。有关更多背景,请参见评论。一种建议是使用printf而不是print

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

7
这个房间里有很多awk的爱!我喜欢这样的简单脚本,只需将$ 1更改为$ 2,便可以修改以添加第二列数据
Paul Dixon

2
没有实际的限制,因为它将把输入作为流处理。因此,如果它可以处理X行文件,则可以确定它可以处理X + 1。
保罗·迪克森

4
我曾经写过一个基本的邮件列表处理程序,其中包含通过Vacation实用程序运行的awk脚本。美好时光。:)
LS 2012年

2
只是用于以下目的:计算所有文档的页面脚本:ls $@ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk '{s+=$2} END {print s}'
飞羊

8
注意,它不适用于大于2147483647(即2 ^ 31)的数字,这是因为awk使用32位带符号整数表示形式。使用awk '{s+=$1} END {printf "%.0f", s}' mydatafile代替。
Giancarlo Sportelli,2015年

665

粘贴通常会合并多个文件的行,但也可以用于将文件的各个行转换为单个行。分隔符标志允许您将x + x类型方程式传递给bc。

paste -s -d+ infile | bc

或者,当从标准输入进行配管时,

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

1
非常好!我会在“ +”之前放置一个空格,只是为了帮助我更好地解析它,但这对于通过粘贴和bc传递一些内存编号非常方便。
Michael H. 2010年

73
比awk解决方案更容易记住和键入。另外,请注意,paste可以使用破折号-作为文件名-这将允许您将命令输出中的数字通过管道传递到粘贴的标准输出中,而无需先创建文件:<commands> | paste -sd+ - | bc
George

19
我有一个拥有1亿个数字的文件。awk命令需要21秒;paste命令需要41秒。但是很高兴遇到“粘贴”!
阿比(Abhi)2013年

4
@Abhi:有趣的:DI猜想我要花20秒钟才能找到awk命令,所以它会一直均衡,直到我尝试输入1亿个数字为止:D
Mark K Cowan 2014年

6
@George您可以忽略-。(如果您想将文件 stdin 结合使用,则很有)。
Alois Mahdal

128

Python的一线版:

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

上面的一类代码不适用于sys.argv []中的文件,但是那一类确实可以stackoverflow.com/questions/450799/…–
jfs

没错-作者说他打算将另一个脚本的输出通过管道传递给命令,而我正尝试使其尽可能短:)
dF。

39
较短的版本是python -c"import sys; print(sum(map(int, sys.stdin)))"
jfs

4
我喜欢这个答案,因为它易于阅读且具有灵活性。我需要目录集合中小于10Mb的文件的平均大小,并将其修改为: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 2012年

1
如果您在其中混入了一些文本,也可以过滤掉非数字:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))
Granitosaurus

91

我会对公认的解决方案大加警告:

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

这是因为awk使用这种形式使用32位有符号整数表示形式:如果总和超过2147483647(即2 ^ 31),它将溢出。

一个更通用的答案(用于求和整数)将是:

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

为什么printf()在这里有帮助?在此之前,会发生int的溢出,因为求和代码是相同的。
罗伯特·克莱姆

9
因为问题实际上出在“打印”功能上。Awk使用64位整数,但出于某些原因,print会将其缩放到32位。
Giancarlo Sportelli'3

4
打印错误似乎已修复,至少对于awk 4.0.1和bash 4.3.11来说是固定的,除非我弄错了:echo -e "2147483647 \n 100" |awk '{s+=$1}END{print s}'显示2147483747
Xen2050

4
使用浮点数会带来一个新问题:echo 999999999999999999 | awk '{s+=$1} END {printf "%.0f\n", s}'生产1000000000000000000
Patrick

1
不应该仅在64位系统上使用“%ld”来使printf截断为32位吗?正如@Patrick指出的那样,在这里浮动并不是一个好主意。
yerforkferchips


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

请注意,应将带有负号前缀的负数转换为dc,因为它使用_前缀而不是-前缀。例如,通过tr '-' '_' | dc -f- -e '...'

编辑:由于此答案获得了很多票“默默无闻”,这里是详细的解释:

该表达式[+z1<r]srz1<rp 执行以下操作

[   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

作为伪代码:

  1. 将“ add_top_of_stack”定义为:
    1. 从堆栈中删除两个顶部值,然后将结果加回去
    2. 如果堆栈具有两个或多个值,请递归运行“ add_top_of_stack”
  2. 如果堆栈具有两个或多个值,请运行“ add_top_of_stack”
  3. 打印结果,现在是堆栈中剩下的唯一项目

为了真正理解的简单性和功能dc,下面是一个有效的Python脚本,该脚本实现了上述命令的某些命令dc并执行了上述命令的Python版本:

### 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只是选择使用的工具。但是我会用更少的堆栈操作来做到这一点。假设所有行确实包含一个数字:(echo "0"; sed 's/$/ +/' inp; echo 'pq')|dc
ikrabbe 2015年

5
在线算法:dc -e '0 0 [+?z1<m]dsmxp'。因此,我们不会在处理之前将所有数字保存在堆栈中,而是一一读取和处理它们(更精确地说,一行一行,因为一行可以包含多个数字)。请注意,空行可以终止输入序列。
ruvim

@ikrabbe太好了。实际上,它可以再缩短一个字符:sed替换中的空格可以删除,因为dc 它并不关心参数和运算符之间的空格。(echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc
WhiteHotLoveTiger

58

jq

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

7
我之所以喜欢它,是因为我猜它很短很短,我也许真的可以记住它。
Alfe

46

纯净的和短的打击。

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

9
这是最好的解决方案,因为如果用替换第一行,它不会创建任何子流程f=$(<numbers.txt)
loentar

1
从stdin获得输入的任何方式?喜欢从烟斗上来吗?
njzk2 2014年

@ njzk2如果放入f=$(cat); echo $(( ${f//$'\n'/+} ))脚本,则可以将任何内容通过管道传递到该脚本,也可以调用不带参数的交互式stdin输入(以Control-D终止)。
mklement0 2014年

5
@loentar这<numbers.txt是一个改进,但是,总的来说,此解决方案仅对小型输入文件有效。例如,使用1,000条输入行的文件,awk在我的计算机上可接受的解决方案的速度大约是20倍-并且还消耗更少的内存,因为该文件不会一次全部读取。
mklement0 2014年

2
当我达到这一目标时,我几乎失去了希望。纯扑!
Omer Akhter,2015年

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

4
然后我将它们加回去:“ -l”确保输出以LF终止,如shell''反引号,并且大多数程序都期望,并且“ <”表示此命令可以在管道中使用。
j_random_hacker

你是对的。借口:Perl单行代码中的每个字符都需要我进行脑力劳动,因此我更喜欢剥离尽可能多的字符。在这种情况下,这种习惯是有害的。
jfs

2
不能将所有内容都加载到RAM的少数解决方案之一。
Erik Aronesty '16

28

我的十五美分:

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

例:

$ 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

我的输入内容可能包含空白行,因此我使用了您在此处发布的内容以及一个grep -v '^$'。谢谢!
James Oravec 2015年

哇!!您的答案是惊人的!我个人的最爱
-thahgr

喜欢这个,并为管道+1。对我来说,非常简单的解决方案
Gelin Luo

24

我对现有答案做了一个快速基准测试

  • 只使用标准工具(对不起,一样的东西luarocket),
  • 是真正的一线客
  • 有能力增加大量的数字(1亿),并且
  • 速度很快(我忽略了花费一分钟以上的时间)。

我总是将1到1亿的数字相加,这在我的计算机上可以在不到一分钟的时间内完成几种解决方案。

结果如下:

蟒蛇

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

粘贴和密件抄送

这用完了我机器上的内存。它的工作量仅为输入量(5,000万个数字)的一半:

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

因此,我想一亿个数字大约需要35秒钟。

佩尔

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

红宝石

:; 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的速度当然是8s最快,但是Pypy解决方案只增加了很少的开销,约为11s的30%。但是,公平地说,Pypy并非完全标准。大多数人只安装了CPython,速度明显慢(22秒),与流行的Awk解决方案一样快。

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


2
paste+ bc的办法正是我一直在寻找到和十六进制值,谢谢!
Tomislav Nakic-Alfirevic '17

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::<i64>().unwrap(); } println!("{}", sum); }
Jocelyn

很棒的答案。而不是无视,但是如果您决定包括那些运行时间较长的结果,那么答案将是更加惊人!
史蒂文·卢

@StevenLu我觉得答案会更长,因此不太好(用您的话说)。但是我可以理解,这种感觉不需要大家共享:)
Alfe

下一个:numba +并行化
gerrit


17

BASH解决方案,如果要将此命令用作命令(例如,如果需要经常执行此操作):

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

然后用法:

addnums < /tmp/nums


11

以下是bash的工作原理:

I=0

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

echo $I

1
当文件可以任意大时,应谨慎使用命令扩展。如果numbers.txt为10MB,则该cat numbers.txt步骤将成问题。
Giacomo

1
确实,但是(如果不是这里找到的更好的解决方案)我将使用它直到我实际遇到该问题。
Francisco Canedo

11

您可以使用num-utils,尽管对于您所需的功能而言可能有些过分。这是一组用于在Shell中处理数字的程序,可以做一些漂亮的事情,包括将它们加起来。有点过时了,但是它们仍然可以工作,如果您需要做更多的事情,可能会很有用。

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


范例:numsum numbers.txt
AGC 2016年

9

我意识到这是一个老问题,但是我很喜欢这个解决方案以分享它。

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

如果有兴趣,我将解释其运作方式。


10
请不要。我们喜欢假装-n和-p是很好的语义内容,而不仅仅是一些聪明的字符串粘贴;)
hobbs 09-10-15

2
是的,请解释一下:)(我不是Perl typea家伙。)
Jens

1
尝试运行“ perl -MO = Deparse -lpe'$ c + = $ _} {$ _ = $ c'”并查看输出,基本上-l使用换行符以及输入和输出分隔符,并且-p打印每行。但是为了执行“ -p”,perl首先添加了一些样板(-MO = Deparse)将向您显示,但随后它只是替换和编译。因此,您可以在'} {'部分插入一个额外的块,并诱使它不要在每一行上打印,而要在最后打印。
Nym

9

纯粹的打击和一线:-)

$ 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

由于猫,并不是真正纯正的扑打。通过用cat代替cat使其纯净$(< numbers.txt)
扑打


6

替代的纯Perl,相当易读,不需要软件包或选项:

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

或更短一点:perl -e'map {$ x + = $ _} <>; 打印$ x'infile.txt
Avi Tevet

大量输入的1000万个数字所需的内存几乎为2GB
Amit Naidu


5

不能避免提交以下内容:

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

在这里可以找到:
最优雅的unix shell单线求和任意精度的数字列表?

与awk,bc和朋友相比,这是它的特殊优势:

  • 它不依赖于缓冲,因此不会因很大的输入而阻塞
  • 它表示没有特定的精度-或该问题的整数大小-限制
  • 如果需要添加浮点数,则无需其他代码

请在答案中包含与问题相关的代码,而不要参考链接
Ibo

5

使用GNU datamash util

seq 10 | datamash sum 1

输出:

55

如果输入数据不规则,并且空格和制表符放在奇数个位置,这可能会造成混淆datamash,则可以使用以下-W开关:

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

...或用于tr清理空白:

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


3

如果您愿意的话,可以用python来做:

未经测试,只需输入:

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

塞巴斯蒂安指出了一个衬里脚本:

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

python -c“ from fileinput import input; print sum(map(int,input())))numbers.txt
jfs,2009年

2
猫被过度使用,从文件中重定向标准输入:python -c“ ...” <numbers.txt
Giacomo

2
@rjack:cat用于演示该脚本适用于stdin和argv []中的文件(如while(<>)Perl)。如果您的输入在文件中,则不需要'<'。
jfs

2
但是< numbers.txt证明它在stdin上也能正常cat numbers.txt |工作。而且它不会教坏习惯。
熊加米奥夫

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

或者,您可以在命令行中输入数字:

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

但是,此文件会使文件变得很粗糙,因此在大文件上使用它不是一个好主意。请参阅j_random_hacker的答案,该答案可以避免


3

以下应能工作(假设您的电话号码是每行的第二个字段)。

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

2
您实际上并不需要{sum = 0}部分
Uphill_ 2011年

3

球拍内衬:

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

3

C(未简化)

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)

我不得不对此评论表示反对。答案没有错-很好。但是,为了表明评论使答案很棒,我只是在评论。
bballdave025

3

预先道歉以提高反引号(“`”)的可读性,但是这些词法在除bash之外的其他shell中起作用,因此更易于粘贴。如果您使用接受它的外壳,则$(command ...)格式比“ command ...”更具可读性(因此可调试),因此可以随意进行修改。

我的bashrc中有一个简单的函数,它将使用awk计算许多简单的数学项

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

这会做+,-,*,/,^,%,sqrt,sin,cos,括号....(以及更多取决于您的awk版本)...您甚至可以看上printf和格式浮点输出,但这就是我通常需要的

对于这个特定的问题,我只需要对每一行都这样做:

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

所以总结每一行的代码块看起来像这样:

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

那就是如果您只想逐行求和。但是对于数据文件中的每个数字

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

顺便说一句,如果我需要在桌面上快速进行操作,请使用以下命令:

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

2
您正在使用哪种不支持的古代外壳$()
nyuszika7h 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.