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

Shell command to sum integers, one per line?

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

作为一点背景,我有一个包含计时测量的日志文件,因此通过对相关行进行grepping,以及一点sed重新格式化,我可以列出该文件中的所有计时。不过,我想算出总数,我的脑子里一片空白,我可以把这个中间输出连接到任何命令上,以完成最后的求和。我过去一直使用expr,但除非它以rpn模式运行,否则我认为它无法应付这种情况(即使这样也很棘手)。

我错过了什么?考虑到可能有几种方法可以实现这一点,我很乐意阅读(并赞成)任何有效的方法,即使其他人已经发布了完成这项工作的不同解决方案。

相关问题:在Unix上计算输出列总和的最短命令?(安德鲁的学分)

更新:哇,正如预期的,这里有一些不错的答案。看来,作为一个通用的命令行工具,我一定要对awk进行更深入的检查!


一点锥子应该做吗?

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

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

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


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

1
paste -s -d+ infile | bc

或者,当从stdin进行管道连接时,

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


python中的一行程序版本:

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


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

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

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

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

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

另外,我本想对第一个答案发表评论,但我没有足够的声誉。


平原狂欢:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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


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

注意,以减号为前缀的负数应该翻译为dc,因为它使用_前缀,而不是-前缀。例如,通过tr '-' '_' | dc -f- -e '...'

编辑:因为这个答案得到了很多选票,所以下面是一个详细的解释:

表达式[+z1执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
[   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

作为伪代码:

  • 将"添加栈顶"定义为:
  • 从堆栈中移除两个顶部值并将结果添加回
  • 如果堆栈有两个或多个值,则递归运行"添加堆栈的顶部"
  • 如果堆栈有两个或多个值,请运行"添加堆栈的顶部"
  • 打印结果,现在是堆栈中唯一剩下的项
  • 为了真正理解dc的简单性和强大性,这里有一个可工作的python脚本,它实现了dc中的一些命令,并执行了上述命令的python版本:

    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
    ### 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()


    使用JQ:

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


    纯粹而短暂的狂欢。

    1
    2
    3
    f=$(cat numbers.txt)
    echo $(( ${f//$'
    '/+} ))


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


    我的十五美分:

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

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    $ 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


    bash解决方案,如果您想将其作为命令(例如,如果您需要经常这样做的话):

    1
    2
    3
    4
    5
    6
    7
    addnums () {
      local total=0
      while read val; do
        (( total += val ))
      done
      echo $total
    }

    然后使用:

    1
    addnums < /tmp/nums

    我已经在现有答案上做了一个快速基准测试,

    • 只使用标准工具(对luarocket之类的东西感到抱歉)。
    • 是真正的一句话,
    • 能够增加大量的数字(1亿),以及
    • 速度很快(我忽略了那些花了一分钟以上的时间)。

    我总是添加1到1亿个数字,这在我的机器上可以在不到一分钟的时间内实现几个解决方案。

    结果如下:

    Python

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    :; 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

    1
    2
    3
    :; seq 100000000 | awk '{s+=$1} END {print s}'
    5000000050000000
    # 22s

    粘贴与BC

    我的机器内存不足。它的工作规模只有输入的一半(5000万个数字):

    1
    2
    3
    4
    5
    6
    :; seq 50000000 | paste -s -d+ - | bc
    1250000025000000
    # 17s
    :; seq 50000001 100000000 | paste -s -d+ - | bc
    3750000025000000
    # 18s

    所以我想1亿个数字大概需要35秒。

    珀尔

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

    红宝石

    1
    2
    3
    :; seq 100000000 | ruby -e"puts ARGF.map(&:to_i).inject(&:+)"
    5000000050000000
    # 30s

    C

    为了比较起见,我编译了C版本并测试了它,只是想知道基于工具的解决方案有多慢。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #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
    ", sum);
        return 0;
    }

    nbsp;

    1
    2
    3
    :; seq 100000000 | ./a.out
    5000000050000000
    # 8s

    结论

    c当然是8s速度最快的,但是pypy解决方案只增加了大约30%到11s的一点开销,但公平地说,pypy并不完全是标准的。大多数人只安装了cpython,它的速度明显较慢(22秒),与流行的awk解决方案一样快。

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


    素巴什一衬

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ cat > /tmp/test
    1
    2
    3
    4
    5
    ^D

    $ echo $(( $(cat /tmp/test | tr"
    ""+" ) 0 ))


    您可以使用num-utils,尽管它可能会对您需要的东西造成过度杀伤力。这是一组用于在shell中操作数字的程序,可以做一些漂亮的事情,当然包括将它们相加。这有点过时了,但它们仍然有效,如果你需要做更多的事情,它们也会很有用。

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


    bash中的以下工作:

    1
    2
    3
    4
    5
    6
    7
    8
    I=0

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

    echo $I


    我认为awk是你想要的:

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

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


    1
    sed 's/^/.+/' infile | bc | tail -1

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

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

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


    纯bash和一行程序中:—)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ 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


    红宝石情人

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

    可选纯Perl,可读性较好,不需要任何软件包或选项:

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


    我的版本:

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


    如果你觉得舒服,你可以用python来做:

    未测试,只键入:

    1
    2
    3
    4
    5
    6
    out = open("filename").read();
    lines = out.split('
    ')
    ints = map(int, lines)
    s = sum(ints)
    print s

    塞巴斯蒂安指出,一行文字:

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


    以下内容应该有效(假设您的数字是每行的第二个字段)。

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


    球拍中有一个衬垫:

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

    C(不简化)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    ", sum);
        return 0;
    }
    EOF)


    无法避免提交此:

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

    在这里可以找到:最优雅的unix shell一行程序来求和任意精度的数字列表?

    与awk、bc和friends相比,它的特殊优势如下:

    • 它不依赖于缓冲,因此不会因输入太大而窒息。
    • 这意味着没有特别的精度或整数大小限制。
    • 如果需要添加浮点数,则无需使用不同的代码


    您可以使用首选的"expr"命令,只需先稍微混合一下输入即可:

    1
    2
    3
    seq 10 | tr '[
    ]' '+' | sed -e 's/+/ + /g' -e's/ + $/
    /' | xargs expr

    这个过程是:

    • "tr"用+符号替换eoln字符,
    • SED用两边的空格填充"+",然后从行中去掉最后的"+"
    • xargs将管道输入插入到命令行中,以便expr使用。

    实时总结,让您监控一些数字处理任务的进度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    $ 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。)


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

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

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

    但是,这一个会弄脏文件,因此在大型文件上使用不是一个好主意。参见J_Random_Hacker的回答,避免含糊不清。


    C++(简体):

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

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

    SCC是shell提示符下的C++代码段评估器


    为backticks("`")的可读性提前道歉,但这些代码在bash之外的shell中工作,因此更易于粘贴。如果您使用接受它的shell,那么$(command…)格式的可读性(因而可调试)要比'command…'格式高得多,因此请随意修改以保持清醒。

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

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

    这将执行+、-、*、/、^、%、sqrt、sin、cos、括号….(以及更多操作,具体取决于您的awk版本)。您甚至可以对printf和格式浮点输出感兴趣,但这正是我通常需要的。

    对于这个特定的问题,我只需为每一行执行以下操作:

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

    因此,每行求和的代码块如下所示:

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

    如果你只想逐行求和的话。但是,对于数据文件中每个数字的总数

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

    顺便说一句,如果我需要在桌面上快速地做一些事情,我使用这个:

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


    使用env变量tmp

    1
    2
    3
    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

    谢谢。


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

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


    所有基于Fedora的系统(Fedora、Rhel、Centos、Korora等)上都有一个Lua解释器,因为它嵌入了RPM包(包管理器RPM的包),即RPM Lua),如果你想学习Lua,这种问题是理想的(你也可以完成你的工作)。

    1
    cat filname | lua -e"sum = 0;for i in io.lines() do sum=sum+i end print(sum)"

    它起作用了。Lua很冗长,不过,你可能不得不忍受一些重复的键盘敲击伤害:)


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

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

    要从stdin计算总和,可以使用以下命令:

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

    一个篮板内胆:

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

    不幸的是,上面的代码还不能在REBOL3中工作(输入没有流式stdin)。

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

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

    …和PHP版本,只是为了完整性

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


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>

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

    一个简单的解决方案是编写一个程序来为您完成这项工作。这可能在python中很快就能完成,比如:

    1
    2
    3
    4
    5
    sum = 0
    file = open("numbers.txt","R")
    for line in file.readlines(): sum+=int(line)
    file.close()
    print sum

    我还没有测试过这个代码,但看起来不错。只需将numbers.txt更改为文件名,将代码保存到名为sum.py的文件中,并在控制台中键入"python sum.py"


    使用GNU datamash工具:

    1
    seq 10 | datamash sum 1

    输出:

    1
    55

    简单PHP

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