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

Posted

技术标签:

【中文标题】Shell命令对整数求和,每行一个?【英文标题】:Shell command to sum integers, one per line? 【发布时间】:2010-10-01 20:04:43 【问题描述】:

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

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

我怎样才能得到整数的总和?

【问题讨论】:

这和我前段时间问的一个问题很相似:***.com/questions/295781/… 这个问题感觉像是代码高尔夫的问题。 codegolf.stackexchange.com :) 【参考方案1】:

不能避免提交这个,这是这个问题最通用的方法,请检查:

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

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

Most elegant unix shell one-liner to sum list of numbers of arbitrary precision?

以下是它相对于 awkbcperlGNU 的 datamash 和朋友的特殊优势:

它使用任何 unix 环境中常见的标准实用程序 它不依赖于缓冲,因此它不会被非常长的输入阻塞这意味着没有特定的精度限制 - 或者整数大小 - 各位 AWK 朋友您好! 不需要不同的代码,如果需要添加浮点数,而不是理论上它可以在最小的环境中不受阻碍地运行

【讨论】:

请在答案中包含与问题相关的代码,不要引用链接 它也恰好比所有其他解决方案慢得多,比 datamash 解决方案慢 10 倍以上 @GabrielRavier OP 并未将速度定义为首要要求,因此如果没有此要求,则首选通用工作解决方案。供参考。 datamash 并不是所有 Unix 平台的标准,fi。 MacOSX 似乎缺少这一点。 @fgeorgatos 这是真的,但我只是想向所有关注这个问题的人指出,与大多数 Linux 系统相比,这个答案实际上非常慢。 @GabrielRavier 您能否提供一些测量数据进行比较?顺便提一句。我已经运行了几个jot 测试,即使对于相当大的列表,速度也非常合理。顺便提一句。如果将 datamash 作为 OP 问题的解决方案,那么任何编译的汇编程序也应该是可以接受的......这会加快速度!【参考方案2】:

这是一个漂亮而干净的Raku(以前称为 Perl 6)单行:

say [+] slurp.lines

我们可以这样使用它:

% seq 10 | raku -e "say [+] slurp.lines"
55

它是这样工作的:

slurp 默认从标准输入读取,不带任何参数;它返回一个字符串。对字符串调用 lines method 会返回该字符串的行列表。

+ 周围的括号将+ 转换为reduction meta operator,这列表缩减为单个值:列表中值的总和。 say 然后用换行符将其打印到标准输出。

需要注意的一点是,我们从不将行显式转换为数字——Raku 足够聪明,可以为我们做到这一点。然而,这意味着我们的代码在输入肯定不是数字时会中断:

% echo "1\n2\nnot a number" | raku -e "say [+] slurp.lines"
Cannot convert string to number: base-10 number must begin with valid digits or '.' in '⏏not a number' (indicated by ⏏)
  in block <unit> at -e line 1

【讨论】:

say [+] lines 其实就够了:-) @ElizabethMattijsen:酷!这是怎么回事? 不带任何参数的 lines 与不带任何语义的 slurp 具有相同的语义,但它会生成 StrSeq,而不是单个 Str【参考方案3】:

使用GNU datamash util:

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

【讨论】:

【参考方案4】:

您可以使用 num-utils,尽管它可能超出您的需要。这是一组用于在 shell 中处理数字的程序,可以做一些漂亮的事情,当然包括将它们相加。它有点过时了,但它们仍然有效,如果您需要做更多的事情,它们会很有用。

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

使用起来真的很简单:

$ seq 10 | numsum
55

但是大量输入的内存不足。

$ seq 100000000 | numsum
Terminado (killed)

【讨论】:

示例:numsum numbers.txt. 管道示例:printf "%s\n" 1 3 5 | numsum【参考方案5】:

好的,这是在 PowerShell 中的操作方法(PowerShell 核心,应该可以在 Windows、Linux 和 Mac 上运行)

Get-Content aaa.dat | Measure-Object -Sum

【讨论】:

这个问题被标记为 [[shell]]:“如果没有特定的标记,则应该假定一个可移植的(POSIX 兼容)解决方案”而不是 PowerShell【参考方案6】:

我要对普遍认可的解决方案提出一个重大警告:

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

【讨论】:

因为问题其实出在“打印”功能上。 awk 使用 64 位整数,但由于某种原因 print 将它们缩小为 32 位。 打印错误似乎已修复,至少对于 awk 4.0.1 和 bash 4.3.11,除非我弄错了:echo -e "2147483647 \n 100" |awk 's+=$1ENDprint s' 显示 2147483747 使用浮点数只是引入了一个新问题:echo 999999999999999999 | awk 's+=$1 END printf "%.0f\n", s' 产生 1000000000000000000 不应该只在 64 位系统上使用“%ld”来避免将 printf 截断为 32 位吗?正如@Patrick 指出的那样,浮动在这里不是一个好主意。 @yerforkferchips,%ld 应该放在代码的什么位置?我试过echo -e "999999999999999999" | awk 's+=$1 END printf "%ld\n", s',但它仍然产生1000000000000000000【参考方案7】:

BASH 解决方案,如果你想把它变成一个命令(例如,如果你需要经常这样做):

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

然后用法:

addnums < /tmp/nums

【讨论】:

【参考方案8】:

为反引号(“`”)的可读性提前道歉,但这些在 bash 以外的 shell 中工作,因此更易于粘贴。如果您使用接受它的 shell,则 $(command ...) 格式比 `command ...` 更具可读性(因此可调试),因此请随意修改以保持理智。

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

calc()
  awk 'BEGINprint '"$@"' '

这将执行 +,-,*,/,^,%,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

【讨论】:

你用的什么古代shell不支持$()【参考方案9】:

我已经对现有答案进行了快速基准测试

仅使用标准工具(对于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

红宝石

:; 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 解决方案仅在 11 秒内增加了大约 30% 的少量开销。但是,公平地说,Pypy 并不完全是标准的。大多数人只安装了 CPython,它的速度要慢得多(22 秒),与流行的 Awk 解决方案一样快。

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

【讨论】:

paste + bc 方法正是我想要对十六进制值求和的方法,谢谢! 只是为了好玩,在 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::&lt;i64&gt;().unwrap(); println!("", sum); 很棒的答案。不要吹毛求疵,但如果您决定包含那些运行时间较长的结果,那么答案会更棒! @StevenLu 我觉得答案会更长,因此不太棒(用你的话来说)。但我可以理解这种感觉不需要每个人都分享:) 下一步:numba + 并行化【参考方案10】:

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

【讨论】:

较小的一个班轮:***.com/questions/450799/… @rjack,num 是在哪里定义的?我相信它以某种方式连接到&lt; numbers.txt 表达式,但不清楚如何。【参考方案11】:

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

paste -s -d+ infile | bc

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

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

【讨论】:

非常好!我会在“+”之前放一个空格,只是为了帮助我更好地解析它,但这对于通过 paste & 然后 bc 来管道一些内存数字非常方便。 比 awk 解决方案更容易记住和输入。另外,请注意 paste 可以使用破折号 - 作为文件名 - 这将允许您将命令输出中的数字通过管道传输到粘贴的标准输出中,而无需先创建文件:&lt;commands&gt; | paste -sd+ - | bc跨度> 我有一个包含 1 亿个数字的文件。 awk 命令耗时 21s;粘贴命令需要 41 秒。但是很高兴见到“粘贴”! @Abhi: 有趣:D 我想我需要 20 多岁才能弄清楚 awk 命令,所以直到我尝试 1 亿和一个数字,它才会变得平衡:D @George 不过,您可以省略-。 (如果您想将文件标准输入结合起来,这很有用)。【参考方案12】:

为了完整起见,还有一个R解决方案

seq 1 10 | R -q -e "f <- file('stdin'); open(f); cat(sum(as.numeric(readLines(f))))"

【讨论】:

【参考方案13】:

Python 中的单行版本:

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

【讨论】:

上面的单行代码不适用于 sys.argv[] 中的文件,但那行代码可以 ***.com/questions/450799/… 是的——作者说他要将另一个脚本的输出通过管道传输到命令中,我试图让它尽可能短:) 较短的版本是python -c"import sys; print(sum(map(int, sys.stdin)))" 我喜欢这个答案,因为它易于阅读和灵活。我需要一组目录中小于 10Mb 的文件的平均大小,并将其修改为:find . -name '*.epub' -exec stat -c %s '' \; | python -c "import sys; nums = [int(n) for n in sys.stdin if int(n) &lt; 10000000]; print(sum(nums)/len(nums))" 如果混入了一些文本,也可以过滤掉非数字:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))【参考方案14】:

一点awk应该可以吗?

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

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

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

【讨论】:

这个房间里有很多 awk 的爱!我喜欢如何修改像这样的简单脚本,只需将 $1 更改为 $2 即可添加第二列数据 没有实际限制,因为它将输入作为流处理。所以,如果它可以处理 X 行的文件,你可以很确定它可以处理 X+1。 我曾经编写了一个基本的邮件列表处理器,其中包含一个通过假期实用程序运行的 awk 脚本。美好时光。 :) 只是将它用于:计算所有文档的页面脚本:ls $@ | xargs -i pdftk dump_data | grep NumberOfPages | awk 's+=$2 END print s' 小心,它不适用于大于 2147483647(即 2^31)的数字,这是因为 awk 使用 32 位有符号整数表示。请改用awk 's+=$1 END printf "%.0f", s' mydatafile【参考方案15】:

C++(简体):

echo 1..10 | scc 'WRL n+=$0; n'

SCC 项目 - http://volnitsky.com/project/scc/

SCC 是 shell 提示符下的 C++ sn-ps 评估器

【讨论】:

【参考方案16】:

使用环境变量 tmp

tmp=awk -v tmp="$tmp" 'print $tmp" "$1' <filename>|echo $tmp|sed "s/ /+/g"|bc

tmp=cat <filename>|awk -v tmp="$tmp" 'print $tmp" "$1'|echo $tmp|sed "s/ /+/g"|bc

谢谢。

【讨论】:

【参考方案17】:

对于红宝石爱好者

ruby -e "puts ARGF.map(&:to_i).inject(&:+)" numbers.txt

【讨论】:

【参考方案18】:
dc -f infile -e '[+z1<r]srz1<rp'

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

编辑:由于这个答案“默默无闻”得到了这么多票,这里有一个详细的解释:

表达式[+z1&lt;r]srz1&lt;rpdoes the following:

[   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

作为伪代码:

    将“add_top_of_stack”定义为:
      从堆栈中删除两个顶部值并将结果添加回来 如果堆栈有两个或多个值,则递归运行“add_top_of_stack”
    如果堆栈有两个或多个值,请运行“add_top_of_stack” 打印结果,现在是堆栈中剩下的唯一项目

要真正了解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()

【讨论】:

dc 只是选择使用的工具。但我会用更少的堆栈操作来做到这一点。假设所有行都包含一个数字:(echo "0"; sed 's/$/ +/' inp; echo 'pq')|dc 在线算法:dc -e '0 0 [+?z1&lt;m]dsmxp'。所以我们不会在处理之前将所有数字保存在堆栈上,而是一个一个地读取和处理它们(更准确地说,是一行一行,因为一行可以包含多个数字)。请注意,空行可以终止输入序列。 @ikrabbe 太好了。它实际上可以再缩短一个字符:sed 替换中的空格可以删除,因为dc 不关心参数和运算符之间的空格。 (echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc【参考方案19】:

与jq:

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

【讨论】:

有没有办法用rq做到这一点? 我想我知道下一个问题可能是什么,所以我会在这里添加答案:) 计算平均值: seq 10 | jq -s 'add / length' ref here【参考方案20】:

您可以使用您喜欢的“expr”命令,您只需先稍微修改一下输入即可:

seq 10 | tr '[\n]' '+' | sed -e 's/+/ + /g' -e's/ + $/\n/' | xargs expr

流程是:

"tr" 将 eoln 字符替换为 + 符号, sed 在 '+' 两边用空格填充,然后从行中去掉最后一个 + xargs 将管道输入插入命令行以供 expr 使用。

【讨论】:

【参考方案21】:

替代的纯 Perl,可读性强,不需要包或选项:

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

【讨论】:

或更短一点: perl -e 'map $x += $_ ;打印 $x' infile.txt 大量输入 1000 万个数字所需的内存几乎为 2GB【参考方案22】:

您可以使用 Alacon - Alasql 数据库的命令行实用程序来完成。

它适用于 Node.js,所以你需要安装 Node.js 然后 Alasql 包:

要从标准输入计算总和,您可以使用以下命令:

> cat data.txt | node alacon "SELECT VALUE SUM([0]) FROM TXT()" >b.txt

【讨论】:

【参考方案23】:

我的版本:

seq -5 10 | xargs printf "- - %s" | xargs  | bc

【讨论】:

短:seq -s+ -5 10 | bc【参考方案24】:

实时求和,让您监控一些数字运算任务的进度。

$ cat numbers.txt 
1
2
3
4
5
6
7
8
9
10

$ cat numbers.txt | while read new; do total=$(($total + $new)); echo $total; done
1
3
6
10
15
21
28
36
45
55

(在这种情况下无需将$total设置为零。完成后您也无法访问$total。)

【讨论】:

【参考方案25】:
sed 's/^/.+/' infile | bc | tail -1

【讨论】:

【参考方案26】:

简单的php

  cat numbers.txt | php -r "echo array_sum(explode(PHP_EOL, stream_get_contents(STDIN)));"

【讨论】:

【参考方案27】:

Rebol 中的单线:

rebol -q --do 's: 0 while [d: input] [s: s + to-integer d] print s' < infile.txt

不幸的是,上述内容目前在 Rebol 3 中还不起作用(INPUT 不流式传输 STDIN)。

所以这里有一个临时解决方案,它也适用于 Rebol 3:

rebol -q --do 's: 0 foreach n to-block read %infile.txt [s: s + n] print s'

【讨论】:

【参考方案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 '^$'。谢谢! 哇!!你的回答太棒了!我个人最喜欢的胎面 喜欢这个并为管道 +1。对我来说非常简单易用的解决方案【参考方案29】:

...和 ​​PHP 版本,只是为了完整起见

cat /file/with/numbers | php -r '$s = 0; while (true)  $e = fgets(STDIN); if (false === $e) break; $s += $e;  echo $s;'

【讨论】:

可以更短:seq 1 10 | php -r ' echo array_sum(file("php://stdin")) 。 PHP_EOL; '【参考方案30】:
#include <iostream>

int main()

    double x = 0, total = 0;
    while (std::cin >> x)
        total += x;
    if (!std::cin.eof())
        return 1;
    std::cout << x << '\n';

【讨论】:

以上是关于Shell命令对整数求和,每行一个?的主要内容,如果未能解决你的问题,请参考以下文章

linux shell 如何行内排序

Shell expr命令

Shell awk命令

shell脚本编程基础之for循环

Shell脚本 数据清洗

在shell中做数值计算