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

And*_*yle 808 shell

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

作为一个背景,我有一个日志文件,其中包括时序测量,所以通过grepping相关的行,并稍微sed重新格式化,我可以列出该文件中的所有时间.然而,我想计算出总数,而且我的思绪已经变得空白,因为任何命令我都可以将这个中间输出传递到最终总和.我过去总是使用expr它,但除非它运行在sed我认为它不会应付这个(即使那样它也会很棘手).

我错过了什么?鉴于可能有多种方法可以实现这一点,我将很乐意阅读(和expr)任何有效的方法,即使其他人已经发布了一个不同的解决方案来完成这项工作.

相关问题:在Unix上计算一列输出总和的最短命令?(来自@Andrew)


更新:哇,正如所料,这里有一些不错的答案.看起来我一定要进行sed更深入的检查expr!

Pau*_*xon 1241

有点awk应该这样做吗?

awk '{s+=$1} END {print s}' mydatafile
Run Code Online (Sandbox Code Playgroud)

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

awk '{s+=$1} END {printf "%.0f", s}' mydatafile
Run Code Online (Sandbox Code Playgroud)

  • 在这个房间里有很多awk的爱!我喜欢如何通过将$ 1更改为$ 2来修改这样的简单脚本以添加第二列数据 (7认同)
  • 注意,它不适用于大于2147483647的数字(即2 ^ 31),这是因为awk使用32位有符号整数表示.使用`awk'{s + = $ 1} END {printf"%.0f",s}'mydatafile`代替. (7认同)
  • 我曾经写过一个基本的邮件列表处理器,它带有一个通过休假实用程序运行的awk脚本.美好时光.:) (3认同)
  • 没有实际限制,因为它将输入作为流处理.因此,如果它可以处理X行的文件,你可以非常肯定它可以处理X + 1. (2认同)
  • 刚刚使用它来计算:计算所有文档的页面脚本:`ls $ @ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk'{s + = $ 2} END {print s}'` (2认同)
  • 对于像我这样不知道 awk 的人.. 检查这个 [链接](http://doc.infosnel.nl/quickawk.html) 的超级快速介绍 (2认同)
  • @GiancarloSportelli 我只想加 2 美分。您可以在 64 位系统上使用“%ld”将数字保留为 64 位 int,根本不需要转换。`回显“2147483647\n2147483647\n2147483647\n2147483647”| awk '{s+=$1} END {printf("%ld\n", s)}'` (2认同)
  • 请注意,“awk”不知道整数是什么。它以双精度进行所有数学运算。因此,只有“2^53”以内的所有数字都可以表示。从那时起,它就会出错: `awk 'BEGIN{print 2^53-1, 2^53, 2^53+1}' => 9007199254740991 9007199254740992 9007199254740992` (2认同)

小智 643

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

paste -s -d+ infile | bc
Run Code Online (Sandbox Code Playgroud)

或者,当从stdin管道时,

<commands> | paste -s -d+ - | bc
Run Code Online (Sandbox Code Playgroud)

  • 比awk解决方案更容易记忆和输入.另外,请注意`paste`可以使用短划线`-`作为文件名 - 这将允许您将命令输出中的数字输入到paste的标准输出中,而无需先创建文件:`<commands> | 粘贴-sd + - | bc` (72认同)
  • 我有一个包含1亿个号码的文件.awk命令需要21s; paste命令需要41秒.但是很高兴见到'粘贴'! (16认同)
  • @George你可以省略`-`.(如果要将文件*与*stdin组合,则非常有用). (4认同)
  • @Abhi:有意思:DI猜测我需要花20秒才能找到awk命令,所以它会变得均匀,直到我尝试1亿和一个数字:D (3认同)

dF.*_*dF. 122

Python中的单行版本:

$ python -c "import sys; print(sum(int(l) for l in sys.stdin))"
Run Code Online (Sandbox Code Playgroud)

  • 较短的版本是`python -c"import sys; print(sum(map(int,sys.stdin)))"` (36认同)
  • 我喜欢这个答案,因为它易于阅读和灵活.我需要在目录集合中小于10Mb的文件的平均大小,并将其修改为:`find.-name'*.epub'-exec stat -c%s'{}'\; | python -c"import sys; nums = [int(n)for sys.stdin中的n如果int(n)<10000000]; print(sum(nums)/ len(nums))"` (3认同)
  • 如果您混合了一些文本,您也可以过滤掉非数字:`import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))` (2认同)

Gia*_*lli 80

我会对通常认可的解决方案提出一个很大的警告:

awk '{s+=$1} END {print s}' mydatafile # DO NOT USE THIS!!
Run Code Online (Sandbox Code Playgroud)

这是因为在这种形式下,awk使用32位有符号整数表示:对于超过2147483647(即2 ^ 31)的和,它将溢出.

更一般的答案(对于求和整数)将是:

awk '{s+=$1} END {printf "%.0f\n", s}' mydatafile # USE THIS INSTEAD
Run Code Online (Sandbox Code Playgroud)

PS我本来想评论第一个答案,但我没有足够的声誉..

  • 因为问题实际上是在"打印"功能中.Awk使用64位整数,但由于某种原因,打印将它们转换为32位. (8认同)
  • 打印错误似乎是固定的,至少对于awk 4.0.1和bash 4.3.11,除非我弄错了:`echo -e"2147483647 \n 100"| awk'{s + = $ 1} END {print s} '`显示`2147483747` (3认同)
  • 使用浮点数会带来一个新问题:`echo 9999999999999999999999 | awk'{s + = $ 1} END {printf“%.0f \ n”,s}'`产生`1000000000000000000` (3认同)

Gia*_*omo 74

普通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
Run Code Online (Sandbox Code Playgroud)

  • 较小的一个班轮:http://stackoverflow.com/questions/450799/linux-command-to-sum-integers-one-per-line/7720597#7720597 (2认同)
  • @Atcold这是`while`表达式中的'读数'. (2认同)
  • @Atcold `num` 在 while 表达式中定义。`while read XX` 的意思是“使用 `while` 读取一个值,然后将该值存储在 `XX` 中” (2认同)

CB *_*ley 63

dc -f infile -e '[+z1<r]srz1<rp'
Run Code Online (Sandbox Code Playgroud)

请注意,前缀为减号的负数应该被翻译dc,因为它使用_前缀而不是-前缀.例如,via 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
Run Code Online (Sandbox Code Playgroud)

作为伪代码:

  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()
Run Code Online (Sandbox Code Playgroud)

  • 在线算法:`dc -e'0 0 [+?z1 <m] dsmxp'`.因此,我们不会在处理之前将所有数字保存在堆栈上,而是逐个读取和处理它们(更准确地说,逐行处理,因为一行可以包含多个数字).请注意,空行可以终止输入序列. (4认同)
  • dc只是使用的首选工具.但我会用更少的堆栈操作来做到这一点.假设所有行都包含一个数字:`(echo"0"; sed的/ $/+ /'inp; echo'pq')| dc`. (2认同)

ban*_*yan 56

jq:

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

  • 我喜欢这个,因为我觉得它是如此清晰和短暂,以至于我实际上可以记住它. (7认同)

小智 46

纯粹和短暂的bash.

f=$(cat numbers.txt)
echo $(( ${f//$'\n'/+} ))
Run Code Online (Sandbox Code Playgroud)

  • 这是最好的解决方案,因为如果用`f = $(<numbers.txt)`替换第一行,它不会创建任何子进程. (9认同)
  • @loentar` <numbers.txt`是一个改进,但总的来说,这个解决方案只对小输入文件有效; 例如,使用1,000个输入行的文件,接受的`awk`解决方案在我的机器上快20倍 - 并且还消耗更少的内存,因为文件不能一次读取. (5认同)
  • 当我到达这个地方时,我几乎失去了希望。纯粹的狂欢! (2认同)

j_r*_*ker 36

perl -lne '$x += $_; END { print $x; }' < infile.txt
Run Code Online (Sandbox Code Playgroud)

  • 我把它们添加回去:" - l"确保输出是LF终止的,因为shell``反引号和大多数程序所期望的,而"<"表示此命令可以在管道中使用. (4认同)
  • 少数解决方案之一,不会将所有内容加载到RAM中. (2认同)
  • 我发现很好奇的是,与顶级答案(使用非 shell 工具)相比,这个答案是多么被低估——尽管它比那些答案更快、更简单。它的语法与 awk 几乎相同,但速度更快(在另一个投票良好的答案中进行了基准测试)并且没有任何警告,并且它比 python 更短、更简单,而且更快(可以轻松添加灵活性)。人们需要了解所用语言的基础知识,但这适用于任何工具。我明白工具受欢迎的概念,但这个问题与工具无关。所有这些都在同一天发布。 (2认同)

inn*_*rld 28

我十五美分:

$ cat file.txt | xargs  | sed -e 's/\ /+/g' | bc
Run Code Online (Sandbox Code Playgroud)

例:

$ 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
Run Code Online (Sandbox Code Playgroud)


Alf*_*lfe 20

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

  • 只使用标准工具(抱歉lua或类似的东西rocket),
  • 是真正的单行,
  • 能够增加大量的数字(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
Run Code Online (Sandbox Code Playgroud)

AWK

:; seq 100000000 | awk '{s+=$1} END {print s}'
5000000050000000
# 22s
Run Code Online (Sandbox Code Playgroud)

粘贴&Bc

这在我的机器上耗尽了内存.它的工作量只有输入的一半(5000万个数字):

:; seq 50000000 | paste -s -d+ - | bc
1250000025000000
# 17s
:; seq 50000001 100000000 | paste -s -d+ - | bc
3750000025000000
# 18s
Run Code Online (Sandbox Code Playgroud)

所以我想这对于1亿个数字来说需要大约35秒.

Perl的

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

红宝石

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

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;
}
Run Code Online (Sandbox Code Playgroud)

 

:; seq 100000000 | ./a.out 
5000000050000000
# 8s
Run Code Online (Sandbox Code Playgroud)

结论

C当然是最快的8s,但Pypy解决方案只增加了大约30%到11s的非常小的开销.但是,公平地说,Pypy并不完全是标准的.大多数人只安装了CPython,速度要慢得多(22秒),与流行的Awk解决方案一样快.

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

  • `paste` + `bc` 方法正是我想要对十六进制值求和的方法,谢谢! (2认同)

Jay*_*Jay 17

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

addnums () {
  local total=0
  while read val; do
    (( total += val ))
  done
  echo $total
}
Run Code Online (Sandbox Code Playgroud)

然后用法:

addnums < /tmp/nums
Run Code Online (Sandbox Code Playgroud)


Kha*_*din 16

普通bash一个衬垫

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

$ echo $(( $(cat /tmp/test | tr "\n" "+" ) 0 ))
Run Code Online (Sandbox Code Playgroud)

  • 不需要_cat_:`echo $(($(tr"\n""+"</ tmp/test)0))` (4认同)
  • `tr` 不完全是“普通的 Bash”/nitpick (3认同)

Pao*_*olo 12

我认为AWK正是您所寻找的:

awk '{sum+=$1}END{print sum}'
Run Code Online (Sandbox Code Playgroud)

您可以通过将数字列表传递到标准输入或将包含数字的文件作为参数传递来使用此命令.

  • 这是一个重复:http://stackoverflow.com/questions/450799/linux-command-to-sum-integers-one-per-line#450821 (2认同)

Fra*_*edo 11

以下在bash中有效:

I=0

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

echo $I
Run Code Online (Sandbox Code Playgroud)


syk*_*ora 11

你可以使用num-utils,虽然它可能对你需要的东西有点过分.这是一组用于操作shell中的数字的程序,可以做几个很好的事情,当然包括添加它们.它有点过时,但它们仍然有用,如果你需要做更多的事情,它们会很有用.

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


agc*_*agc 11

使用GNU datamash UTIL

seq 10 | datamash sum 1
Run Code Online (Sandbox Code Playgroud)

输出:

55
Run Code Online (Sandbox Code Playgroud)

如果输入数据不规则,在奇数位置有空格和制表符,这可能会混淆datamash,然后使用-W开关:

<commands...> | datamash -W sum 1
Run Code Online (Sandbox Code Playgroud)

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

<commands...> | tr -d '[[:blank:]]' | datamash sum 1
Run Code Online (Sandbox Code Playgroud)

如果输入足够大,输出将采用科学计数法。

seq 100000000 | datamash sum 1
Run Code Online (Sandbox Code Playgroud)

输出:

5.00000005e+15
Run Code Online (Sandbox Code Playgroud)

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

seq 100000000 | datamash  --format '%.0f' sum 1
Run Code Online (Sandbox Code Playgroud)

输出:

5000000050000000
Run Code Online (Sandbox Code Playgroud)


小智 9

sed 's/^/.+/' infile | bc | tail -1
Run Code Online (Sandbox Code Playgroud)


Nym*_*Nym 9

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

% cat > numbers.txt
1 
2 
3 
4 
5
^D
% cat numbers.txt | perl -lpe '$c+=$_}{$_=$c'
15
Run Code Online (Sandbox Code Playgroud)

如果有兴趣,我会解释它是如何工作的.

  • 请不要.我们喜欢假装-n和-p是很好的语义事物,而不仅仅是一些聪明的字符串粘贴;) (9认同)
  • 是的,请解释一下:)(我不是Perl typea家伙。) (2认同)
  • 尝试运行“perl -MO=Deparse -lpe '$c+=$_}{$_=$c'”并查看输出,基本上 -l 使用换行符以及输入和输出分隔符,并且 -p 打印每一行。但是为了执行“-p”,perl 首先添加了一些样板(-MO=Deparse)会显示给您,然后它只是替换和编译。因此,您可以使用 '}{' 部分插入一个额外的块,并诱使它不在每一行打印,而是在最后打印。 (2认同)

小智 9

纯粹的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
Run Code Online (Sandbox Code Playgroud)


fge*_*tos 9

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

jot 1000000 | sed '2,$s/$/+/;$s/$/p/' | dc
Run Code Online (Sandbox Code Playgroud)

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

这是它相对于awkbc和朋友的特殊优势:

  • 它不依赖于缓冲,因此它不会因非常长的输入而窒息
  • 它意味着没有特定的精度- 或整数大小 - 限制 - 你好 AWK 用户!
  • 不需要不同的代码,如果需要添加浮点数,而是.


小智 6

替代纯Perl,相当可读,不需要包或选项:

perl -e "map {$x += $_} <> and print $x" < infile.txt
Run Code Online (Sandbox Code Playgroud)


joh*_*nvc 6

对于Ruby Lovers

ruby -e "puts ARGF.map(&:to_i).inject(&:+)" numbers.txt
Run Code Online (Sandbox Code Playgroud)


Jul*_*lia 6

这是一段漂亮、干净的Raku(以前称为 Perl 6)一行:

\n
say [+] slurp.lines\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以像这样使用它:

\n
% seq 10 | raku -e "say [+] slurp.lines"\n55\n
Run Code Online (Sandbox Code Playgroud)\n

它的工作原理如下:

\n

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

\n

周围的括号+变成+一个归约元运算符,它将列表减少为单个值:列表中值的总和。say然后用换行符将其打印到标准输出。

\n

需要注意的一件事是,我们从来没有显式地将行转换为数字\xe2\x80\x94Raku 足够聪明,可以为我们做到这一点。然而,这意味着我们的代码在输入绝对不是数字时中断:

\n
% echo "1\\n2\\nnot a number" | raku -e "say [+] slurp.lines"\nCannot convert string to number: base-10 number must begin with valid digits or \'.\' in \'\xe2\x8f\x8fnot a number\' (indicated by \xe2\x8f\x8f)\n  in block <unit> at -e line 1\n
Run Code Online (Sandbox Code Playgroud)\n