逐行相加整数的Shell命令?

1083

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

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

如何获得整数的总和?


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

1628

用一点awk就可以了吗?

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

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

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

13
这个房间里有很多awk爱好者!我喜欢这样的简单脚本,只需将$1更改为$2即可修改以加总数据的第二列。 - Paul Dixon
5
我曾经用awk脚本编写了一个简单的邮件列表处理器,通过vacation工具来运行。好时光 :)。 - Mr. Lance E Sloan
2
只需将此脚本用于计算所有文档页面的数量:ls $@ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk '{s+=$2} END {print s}' - flying sheep
2
对于像我这样不了解awk的人..请查看此链接(http://doc.infosnel.nl/quickawk.html)进行超快速介绍。 - nedR
11
注意,如果数字大于2147483647(即2的31次方),那么它将无法工作,这是因为awk使用了32位有符号整数表示。请改用awk '{s+=$1} END {printf "%.0f", s}' mydatafile - Giancarlo Sportelli
显示剩余22条评论

778

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

paste -s -d+ infile | bc

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

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

87
比awk解决方案更易记和输入。另外,请注意paste可以使用短横线“-”作为文件名 - 这将允许您将数字从命令输出管道到paste的标准输出,无需先创建文件: <commands> | paste -sd+ - | bc - George
1
太棒了!我刚刚用这个命令来计算所有核心的 Bogomips 总和:cat /proc/cpuinfo | grep bogo | cut -d: -f2 | paste -sd+ | bc - user172783
25
жҲ‘жңүдёҖдёӘеҢ…еҗ«1дәҝдёӘж•°еӯ—зҡ„ж–Ү件гҖӮawkе‘Ҫд»ӨйңҖиҰҒ21з§’пјӣpasteе‘Ҫд»ӨйңҖиҰҒ41з§’гҖӮдёҚиҝҮиҝҳжҳҜеҫҲй«ҳе…ҙи®ӨиҜҶвҖңpasteвҖқе‘Ҫд»ӨпјҒ - Abhi
8
@Abhi:有趣 :D 我猜我需要20秒钟才能想出awk命令,但是当我尝试计算1亿零1个数字时就会变得更加困难 :D - Mark K Cowan
6
你可以省略掉-,不过如果你想要将文件和标准输入合并在一起,它会很有用。 - Alois Mahdal
显示剩余14条评论

156
Python 中的一行代码版本:
$ python -c "import sys; print(sum(int(l) for l in sys.stdin))"

51
简化版的命令是 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
非常灵活的解决方案。也适用于浮点数,只需将“int”替换为“float”。 - geekQ
2
如果您的文本中混杂了一些文本,您也可以过滤掉非数字:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin)) - Granitosaurus
实际上Python是我首选的方式,因为它可以对非常大的数字进行求和而不会舍入结果,而awk则会舍入结果。 - Uri
显示剩余3条评论

124

我会在通常被认可的解决方案上加上一个大警告:

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

14
问题实际上出在“print”函数。Awk使用64位整数,但由于某种原因,print函数会将它们缩减为32位。 - Giancarlo Sportelli
4
打印错误似乎已经修复,至少对于 awk 4.0.1 和 bash 4.3.11 来说是这样,除非我弄错了。执行 echo -e "2147483647 \n 100" |awk '{s+=$1}END{print s}' 命令会输出 2147483747 - Xen2050
8
使用浮点数会引入一个新问题:echo 999999999999999999 | awk '{s+=$1} END {printf "%.0f\n", s}' 会输出 1000000000000000000 - phemmer
1
在64位系统上,仅使用“%ld”不会使printf截断为32位,这应该行得通吧?正如@Patrick所指出的那样,在此处使用浮点数并不是一个好主意。 - yerforkferchips
1
@yerforkferchips,在代码中应该把%ld放在哪里?我尝试了echo -e "999999999999999999" | awk '{s+=$1} END {printf "%ld\n", s}',但它仍然产生了1000000000000000000 - Josh
显示剩余5条评论

96

简单的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

2
一个更简短的一行代码:https://dev59.com/S3RB5IYBdhLWcg3w-789#7720597 - Khaja Minhajuddin
@rjack,num是在哪里定义的?我相信它与< numbers.txt表达式有关,但不清楚具体是怎么回事。 - Atcold
2
@Atcold num 在 while 表达式中被定义。while read XX 的意思是“使用 while 读取一个值,然后将该值存储在 XX 中”。 - aggregate1166877
这个很慢,比C版本慢100倍。 - undefined

95

使用 jq:

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

有没有使用 rq 的方法可以做到这一点? - theonlygusti
我想我知道下一个可能的问题是什么,所以我会在这里添加答案 :) 计算平均值: seq 10 | jq -s 'add / length' 参考链接 - Marinos An
这很慢。jq -s 'add'比C版本慢8倍 - undefined

71
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
5
在线算法:dc -e '0 0 [+?z1<m]dsmxp'。因此,在处理数据之前,我们不会将所有数字保存在堆栈中,而是逐个读取和处理它们(更准确地说,是逐行读取,因为一行可能包含多个数字)。请注意,空行可以终止输入序列。 - ruvim
@ikrabbe 太好了。实际上还可以再缩短一个字符:sed替换中的空格可以删除,因为dc不在意参数和操作符之间的空格。(echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc - WhiteHotLoveTiger
这个很慢。dc -f - -e '[+z1<r]srz1<rp' 比 C 版本慢 250 倍。dc -e '0 0 [+?z1<m]dsmxp' 比 C 版本慢 15 倍。 - undefined

53

纯粹简短的bash。

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

11
如果你将第一行替换为 f=$(<numbers.txt),这是最佳解决方案,因为它不会创建任何子进程。 - loentar
1
有没有一种从stdin获取输入的方法?比如从一个管道中? - njzk2
1
如果您将f=$(cat); echo $(( ${f//$'\n'/+} ))放入脚本中,那么您可以将任何内容传输到该脚本或调用它而无需参数进行交互式标准输入(使用Control-D终止)。 - mklement0
7
“@loentar,<numbers.txt是一种改进,但总的来说,这种解决方案只适用于小型输入文件。例如,对于包含1,000个输入行的文件,在我的机器上,接受的awk解决方案大约快20倍-并且占用的内存也更少,因为文件不是一次性读取的。” - mklement0
1
我没有点踩,但想指出这是一个可怕的解决方案——从1加到99999需要在一台M1 Max和bash 5.2.15的机器上花费26.7秒,而使用jotawk只需要0.053秒,另一个awk生成的结果为0.22秒。即使将每个整数相加到1亿,也只需要11.5秒,而将所有整数相加到10亿只需要1分55秒。perlawk稍微慢了一点。 - RARE Kpop Manifesto
显示剩余3条评论

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

4
我将它们添加回来了:"-l" 确保输出以 LF 结尾,因为 shell `` backticks 和大多数程序都期望这样,"<" 表示此命令可用于管道中。 - j_random_hacker
你是对的。作为借口:在 Perl 的一行代码中,每个字符都需要我进行思考,因此我更喜欢尽可能地减少字符。这个习惯在这种情况下是有害的。 - jfs
4
少数几种不会将所有内容加载到内存中的解决方案。 - Erik Aronesty
1
我觉得很奇怪,这个答案与那些使用非shell工具的最高评价答案相比是如此被低估,尽管它比那些答案更快更简单。它的语法几乎与awk相同,但更快(在另一个得到很高票数的答案中进行了基准测试),没有任何警告,并且比Python更短更简单,也更快(灵活性可以同样轻松地添加)。需要了解用于它的语言的基础知识,但这适用于任何工具。我理解工具的流行度,但这个问题是与工具无关的。所有这些都是在同一天发布的。 - zdim
(关于我上面的评论免责声明:我知道、使用和喜欢 Perl 和 Python,它们是好工具。) - zdim
显示剩余2条评论

38

我的想法:

$ 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
哇!!你的回答太棒了!这是我在整个帖子中最喜欢的回答。 - thahgr
喜欢这个,加一赞管道。对我来说是一个非常简单易用的解决方案。 - Gelin Luo

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