program tip

정수를 합산하는 쉘 명령, 한 줄에 하나씩?

radiobox 2020. 9. 29. 07:33
반응형

정수를 합산하는 쉘 명령, 한 줄에 하나씩?


여러 줄의 텍스트를 입력하고 각 줄에 단일 정수를 포함하고 이러한 정수의 합계를 출력하는 명령을 찾고 있습니다.

약간의 배경 지식으로 타이밍 측정이 포함 된 로그 파일이 있으므로 관련 라인을 검색하고 약간의 sed 재 포맷을 통해 해당 파일의 모든 타이밍을 나열 할 수 있습니다. 그러나 합계를 계산하고 싶지만 최종 합계를 수행하기 위해이 중간 출력을 파이프 할 수있는 명령에 대해 마음이 비어 있습니다. 나는 항상 expr과거에 사용해 왔지만 RPN 모드에서 실행되지 않는 한 이것에 대처할 수 없을 것이라고 생각합니다 (심지어 까다로울 것입니다).

내가 무엇을 놓치고 있습니까? 이를 달성하는 데는 여러 가지 방법이있을 수 있으므로 다른 사람이 이미 해당 작업을 수행하는 다른 솔루션을 게시 했더라도 작동하는 모든 접근 방식을 읽고 기꺼이 읽고 찬성 할 것입니다.

관련 질문 : Unix에서 출력 열의 합계를 계산하는 가장 짧은 명령? (크레딧 @Andrew )


업데이트 : 와우, 예상대로 여기에 좋은 답변이 있습니다. awk일반적으로 명령 줄 도구로서 더 자세히 조사 해야 할 것 같습니다 !


조금 어색해야할까요?

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

참고 : 일부 버전의 awk는 2 ^ 31 (2147483647)을 초과하는 항목을 추가하려는 경우 이상한 동작이 있습니다. 자세한 배경 정보는 주석을 참조하십시오. 한 가지 제안은 다음 printf대신 사용 하는 것입니다 print.

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

붙여 넣기는 일반적으로 여러 파일의 행을 병합하지만 파일의 개별 행을 단일 행으로 변환하는 데 사용할 수도 있습니다. 구분자 플래그를 사용하면 x + x 유형 방정식을 bc에 전달할 수 있습니다.

paste -s -d+ infile | bc

또는 stdin에서 배관 할 때

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

Python의 한 줄 버전 :

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

일반적으로 승인 된 솔루션에 대해 큰 경고를 표시합니다.

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

일반 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

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다음은 dc위 명령의 일부 명령을 구현 하고 Python 버전의 명령을 실행하는 작동하는 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()

JQ :

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

순수하고 짧은 bash.

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

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

내 15 센트 :

$ 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

나는 기존 답변에 대한 빠른 벤치 마크를 수행했습니다.

  • 표준 도구 만 사용하십시오 ( lua또는 같은 항목에 대해서는 죄송합니다 rocket).
  • 진짜 한 줄짜리입니다.
  • 엄청난 수 (1 억)를 추가 할 수 있으며
  • 빠르다 (1 분 이상 걸린 것은 무시했다).

몇 가지 솔루션에 대해 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

붙여 넣기 및 Bc

내 컴퓨터의 메모리가 부족합니다. 입력 크기의 절반 (5 천만 개의 숫자)에서 작동했습니다.

:; 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 버전을 컴파일하고이를 테스트하여 도구 기반 솔루션이 얼마나 느린 지 파악했습니다.

#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 솔루션은 약 30 %에서 11 초까지 약간의 오버 헤드 만 추가합니다 . 그러나 공정하게 말하면 Pypy는 정확히 표준이 아닙니다. 대부분의 사람들은 인기있는 Awk 솔루션만큼 빠른 속도 (22 초) 인 CPython 만 설치했습니다.

표준 도구를 기반으로 한 가장 빠른 솔루션은 Perl (15s)입니다.


BASH 솔루션,이를 명령으로 사용하려는 경우 (예 : 자주 수행해야하는 경우) :

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

그런 다음 사용법 :

addnums < /tmp/nums

플레인 배쉬 원 라이너

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

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

나는 AWK가 당신이 찾고있는 것이라고 생각합니다.

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

표준 입력을 통해 숫자 목록을 전달하거나 숫자가 포함 된 파일을 매개 변수로 전달하여이 명령을 사용할 수 있습니다.


다음은 bash에서 작동합니다.

I=0

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

echo $I

You can using num-utils, although it may be overkill for what you need. This is a set of programs for manipulating numbers in the shell, and can do several nifty things, including of course, adding them up. It's a bit out of date, but they still work and can be useful if you need to do something more.

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


I realize this is an old question, but I like this solution enough to share it.

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

If there is interest, I'll explain how it works.


Pure bash and in a one-liner :-)

$ 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

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

Alternative pure Perl, fairly readable, no packages or options required:

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

For Ruby Lovers

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

My version:

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

Cannot avoid submitting this:

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

It is found over here:
Most elegant unix shell one-liner to sum list of numbers of arbitrary precision?

And here are its special advantages over awk, bc and friends:

  • it does not depend on buffering and thus does not choke with really big inputs
  • it implies no particular precision -or integer size for that matter- limits
  • no need for different code, if floating point numbers need to be added

You can do it in python, if you feel comfortable:

Not tested, just typed:

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

Sebastian pointed out a one liner script:

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

The following should work (assuming your number is the second field on each line).

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

One-liner in Racket:

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

C (not simplified)

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)

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

Or, you can type in the numbers on the command line:

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

However, this one slurps the file so it is not a good idea to use on large files. See j_random_hacker's answer which avoids slurping.


Real-time summing to let you monitor progress of some number-crunching task.

$ 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

(There is no need to set $total to zero in this case. Neither you can access $total after the finish.)


You can use your preferred 'expr' command you just need to finagle the input a little first:

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

The process is:

  • "tr" replaces the eoln characters with a + symbol,
  • sed pads the '+' with spaces on each side, and then strips the final + from the line
  • xargs inserts the piped input into the command line for expr to consume.

참고URL : https://stackoverflow.com/questions/450799/shell-command-to-sum-integers-one-per-line

반응형