Команда оболочки для суммирования целых чисел, по одному на строку?


867

Я ищу команду, которая будет принимать (в качестве входных данных) несколько строк текста, каждая строка содержит одно целое число, и выводит сумму этих целых чисел.

В качестве предыстории у меня есть файл журнала, который включает измерения времени. Посредством поиска соответствующих строк и небольшого sedпереформатирования я могу перечислить все моменты времени в этом файле. Я хотел бы отработать итог. Я могу передать этот промежуточный вывод любой команде, чтобы получить окончательную сумму. Я всегда использовал exprв прошлом, но если он не работает в режиме RPN, я не думаю, что он справится с этим (и даже тогда это будет сложно).

Как я могу получить сумму целых чисел?


2
Это очень похоже на вопрос, который я задал некоторое время назад: stackoverflow.com/questions/295781/…
An̲̳̳drew

5
Мне очень нравится этот вопрос из-за того, что есть много возможных правильных (или хотя бы рабочих) ответов.
Франциско Канедо

Этот вопрос кажется проблемой для кода гольфа. codegolf.stackexchange.com :)
Гордон Бин

Ответы:


1322

Немного аук должно сделать это?

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

Примечание: некоторые версии awk имеют странное поведение, если вы собираетесь добавлять что-либо, превышающее 2 ^ 31 (2147483647). Смотрите комментарии для получения дополнительной информации. Одним из предложений является использование, printfа не print:

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

7
В этой комнате много любви к Нему! Мне нравится, как такой простой сценарий можно изменить, добавив второй столбец данных, просто изменив $ 1 на $ 2
Пол Диксон

2
Практического ограничения нет, поскольку он будет обрабатывать ввод как поток. Итак, если он может обрабатывать файл из X строк, вы можете быть уверены, что он может обрабатывать X + 1.
Пол Диксон

4
Однажды я написал элементарный обработчик списков рассылки с помощью скрипта awk, запускаемого с помощью утилиты для отпуска. Хорошие времена. :)
LS

2
просто использовал это для: Считать все страницы документов скрипт:ls $@ | xargs -i pdftk {} dump_data | grep NumberOfPages | awk '{s+=$2} END {print s}'
Летающая овца

8
Будьте осторожны, он не будет работать с числами, превышающими 2147483647 (т.е. 2 ^ 31), потому что awk использует 32-битное целочисленное представление со знаком. Используйте awk '{s+=$1} END {printf "%.0f", s}' mydatafileвместо этого.
Джанкарло Спортелли

665

Вставка обычно объединяет строки из нескольких файлов, но ее также можно использовать для преобразования отдельных строк файла в одну строку. Флаг разделителя позволяет вам передавать уравнение типа x + x в bc.

paste -s -d+ infile | bc

Кроме того, когда трубопровод от стандартного ввода,

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

1
Очень хорошо! Я бы поставил пробел перед «+», просто чтобы лучше разобрать его, но это было очень удобно для передачи некоторых чисел в памяти через paste & затем bc.
Майкл Х.

73
Намного легче запомнить и напечатать, чем решение awk. Кроме того, обратите внимание, что pasteв -качестве имени файла можно использовать тире - что позволит вам передавать числа из выходных данных команды в стандартный вывод вставки без необходимости сначала создавать файл:<commands> | paste -sd+ - | bc
Джордж

19
У меня есть файл с 100 миллионами номеров. Команда awk занимает 21 с; команда вставки занимает 41 с. Но все же приятно встретить «пасту»!
Абхи

4
@Abhi: Интересно: D, думаю, мне понадобится 20 с, чтобы выяснить команду awk, чтобы она выровнялась, пока я не попробую 100 миллионов и одно число: D
Марк К Коуэн

6
@ Джордж Вы можете опустить -, хотя. (Это полезно, если вы хотите объединить файл со стандартным вводом).
Алоис Махдал

128

Однострочная версия в Python:

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

Выше одной строки не работает для файлов в sys.argv [], но это делает stackoverflow.com/questions/450799/…
jfs

Правда - автор сказал, что он собирался передать вывод из другого скрипта в команду, и я пытался сделать его максимально коротким :)
dF.

39
Более короткая версия будетpython -c"import sys; print(sum(map(int, sys.stdin)))"
JFS

4
Мне нравится этот ответ за его удобство чтения и гибкость. Мне нужен был средний размер файлов размером менее 10 МБ в коллекции каталогов, и я изменил его следующим образом: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))"
Пол Уипп

1
Вы также можете отфильтровать не числа, если у вас есть какой-то текст, смешанный в:import sys; print(sum(int(''.join(c for c in l if c.isdigit())) for l in sys.stdin))
Granitosaurus

91

Я бы поставил большое ПРЕДУПРЕЖДЕНИЕ по общепринятому решению:

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

Почему printf () здесь помогает? Переполнение int произойдет до этого, потому что код суммирования одинаков.
Роберт Klemme

9
Потому что проблема на самом деле в функции «печать». Awk использует 64-битные целые числа, но по какой-то причине print не масштабирует их до 32-битных.
Джанкарло Спортелли

4
Ошибка печати, по-видимому, исправлена, по крайней мере для awk 4.0.1 и bash 4.3.11, если я не ошибаюсь: echo -e "2147483647 \n 100" |awk '{s+=$1}END{print s}'показывает2147483747
Xen2050

4
Использование поплавков только вводит новую проблему: echo 999999999999999999 | awk '{s+=$1} END {printf "%.0f\n", s}'производит1000000000000000000
Патрик

1
Разве не следует использовать «% ld» в 64-битных системах, чтобы не было усеченного printf до 32-битного? Как отмечает @Patrick, поплавки здесь не очень хорошая идея.
yerforkferchips


66
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.
Икраббе

5
Онлайн алгоритм: dc -e '0 0 [+?z1<m]dsmxp'. Поэтому мы не сохраняем все числа в стеке перед обработкой, а читаем и обрабатываем их по одному (точнее, построчно, поскольку одна строка может содержать несколько чисел). Обратите внимание, что пустая строка может завершить последовательность ввода.
ruvim

@ikrabbe это здорово. На самом деле он может быть сокращен еще на один символ: пробел в sedподстановке может быть удален, поскольку dc не заботится о пробелах между аргументами и операторами. (echo "0"; sed 's/$/+/' inputFile; echo 'pq')|dc
WhiteHotLoveTiger

58

С JQ :

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

7
Мне это нравится, потому что я думаю, что это настолько ясно и кратко, что я действительно смогу запомнить это.
Alfe

46

Чистый и короткий удар.

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

9
Это лучшее решение, потому что оно не создает подпроцесса, если вы замените первую строку на f=$(<numbers.txt).
loentar

1
Любой способ ввода от стандартного ввода? как из трубы?
njzk2

@ njzk2 Если вы вставите f=$(cat); echo $(( ${f//$'\n'/+} ))скрипт, то вы можете направить что- нибудь к этому скрипту или вызвать его без аргументов для интерактивного ввода в стандартный ввод (завершите с помощью Control-D).
mklement0

5
@loentar Это <numbers.txtулучшение, но в целом это решение эффективно только для небольших входных файлов; например, с файлом из 1000 строк ввода принятое awkрешение примерно в 20 раз быстрее на моем компьютере - и также потребляет меньше памяти, поскольку файл не читается сразу.
mklement0

2
Я почти потерял надежду, когда достиг этого. Чистый баш!
Омер Ахтер

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

4
И я добавил их обратно: «-l» гарантирует, что вывод заканчивается на LF, как и `backticks оболочки` и большинство программ ожидают, а «<» указывает, что эту команду можно использовать в конвейере.
j_random_hacker

Вы правы. В качестве оправдания: каждый персонаж в однострочниках Perl требует умственной работы для меня, поэтому я предпочитаю убрать как можно больше символов. Привычка была вредна в этом случае.
JFS

2
Одно из немногих решений, которое не загружает все в оперативную память.
Эрик Аронесты

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

Мой ввод может содержать пустые строки, поэтому я использовал то, что вы опубликовали здесь, плюс a grep -v '^$'. Спасибо!
Джеймс Оравец

Ух ты!! ваш ответ потрясающий! мой личный фаворит из всех в протекторе
thahgr

Люблю это и +1 за конвейер. Очень простое и легкое решение для меня
Гелин Ло

24

Я сделал быстрый тест на существующие ответы, которые

  • используйте только стандартные инструменты (извините за такие вещи, как luaилиrocket ),
  • настоящие однострочники,
  • способны добавлять огромное количество чисел (100 миллионов) и
  • быстрые (я проигнорировал те, которые заняли больше минуты).

Я всегда прибавлял числа от 1 до 100 миллионов, что было выполнимо на моей машине менее чем за минуту для нескольких решений.

Вот результаты:

питон

:; 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

Это исчерпало память на моей машине. Это работало для половины размера ввода (50 миллионов чисел):

:; seq 50000000 | paste -s -d+ - | bc
1250000025000000
# 17s
:; seq 50000001 100000000 | paste -s -d+ - | bc
3750000025000000
# 18s

Так что, я думаю, это заняло бы ~ 35 с на 100 миллионов номеров.

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 не совсем стандарт. У большинства людей установлен только CPython, который значительно медленнее (22 с), точно так же быстро, как и популярное решение Awk.

Самым быстрым решением на основе стандартных инструментов является Perl (15 с).


2
paste+ bcПодход был только то , что я искал в сумму шестнадцатеричных значений, спасибо!
Томислав Накич-Альфиревич

1
Просто для удовольствия, в Русте: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::<i64>().unwrap(); } println!("{}", sum); }
Джоселин

отличный ответ. не для того, чтобы придираться, но если вы решите включить эти более продолжительные результаты, ответ будет еще более удивительным!
Стивен Лу

@ StevenLu Я чувствовал, что ответ будет более длинным и, следовательно, менее удивительным (если использовать ваши слова). Но я могу понять, что это чувство не должно быть разделено всеми :)
Alfe

Далее: нумба + распараллеливание
геррит


17

Решение BASH, если вы хотите сделать это командой (например, если вам нужно делать это часто):

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

Тогда использование:

addnums < /tmp/nums

14

Я думаю, что AWK это то, что вы ищете:

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

Вы можете использовать эту команду, передавая список чисел через стандартный ввод или передавая файл, содержащий числа в качестве параметра.


2
jfs

11

Следующие работы в bash:

I=0

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

echo $I

1
Расширение команд следует использовать с осторожностью, когда файлы могут быть произвольно большими. С numbers.txt размером 10 МБ этот cat numbers.txtшаг будет проблематичным.
Джакомо

1
Действительно, однако (если бы не лучшие решения, найденные здесь), я использовал бы это, пока я фактически не столкнулся с этой проблемой.
Франциско Канедо

11

Вы можете использовать num-utils, хотя это может быть излишним для того, что вам нужно. Это набор программ для манипулирования числами в оболочке, и он может делать несколько изящных вещей, включая, конечно, их сложение. Это немного устарело, но они все еще работают и могут быть полезны, если вам нужно сделать что-то большее.

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


Пример: numsum numbers.txt.
АРУ

9

Я понимаю, что это старый вопрос, но мне достаточно этого решения, чтобы поделиться им.

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

Если есть интерес, я объясню, как это работает.


10
Пожалуйста, не надо. Нам нравится делать вид, что -n и -p - это хорошие семантические вещи, а не просто умное
вставление

2
Да, пожалуйста, объясните :) (Я не парень типа Perl.)
Йенс

1
Попробуйте запустить "perl -MO = Deparse -lpe '$ c + = $ _} {$ _ = $ c'" и посмотреть на вывод, в основном -l использует переводы строки и разделители ввода и вывода, а -p печатает каждую строку. Но для того, чтобы сделать '-p', perl сначала добавляет некоторую табличку с котлом (которую -MO = Deparse) покажет вам, но затем он просто заменяет и компилирует. Таким образом, вы можете заставить дополнительный блок вставляться с частью '} {' и обманывать его, чтобы не печатать в каждой строке, а печатать в самом конце.
Nym

9

Чистый баш и в однострочнике :-)

$ 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

Почему есть две ((круглые скобки ))?
Atcold

Не совсем чистый удар из-за кота. сделай это чистым $(< numbers.txt)
ударом,


6

Альтернативный чистый Perl, достаточно читаемый, никаких пакетов или опций не требуется:

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

или чуть короче: perl -e 'map {$ x + = $ _} <>; распечатай $ x 'infile.txt
Ави Тевет

Для большого ввода 10 миллионов номеров требуется почти 2 ГБ памяти
Амит Найду


5

Невозможно избежать отправки этого:

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

Это найдено здесь:
Самая элегантная однострочная оболочка Unix для суммирования списка чисел произвольной точности?

И вот его особые преимущества перед awk, bc и friends:

  • он не зависит от буферизации и поэтому не задыхается при действительно больших входах
  • это не подразумевает особой точности - или целочисленного размера для этого вопроса - ограничивает
  • нет необходимости в другом коде, если необходимо добавить числа с плавающей запятой

Пожалуйста , включите код , связанный с вопросом в ответ и не относится к ссылке
IBO

5

Использование GNU datamash Util :

seq 10 | datamash sum 1

Вывод:

55

Если входные данные нерегулярны, с пробелами и табуляциями в нечетных местах, это может сбить с толку datamash, тогда либо используйте -Wпереключатель:

<commands...> | datamash -W sum 1

... или используйте trдля очистки пробелов:

<commands...> | tr -d '[[:blank:]]' | datamash sum 1


3

Вы можете сделать это в Python, если вам удобно:

Не проверено, просто напечатано:

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

Себастьян указал на один строчный сценарий:

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

python -c "из входных данных для импорта из файла; вывести сумму (map (int, input ()))" numbers.txt
jfs

2
cat перегружен, перенаправить стандартный ввод из файла: python -c "..." <numbers.txt
Giacomo

2
@rjack: catиспользуется для демонстрации того, что скрипт работает как для stdin, так и для файлов в argv [] (как while(<>)в Perl). Если ваш ввод находится в файле, тогда «<» не требуется.
JFS

2
Но < numbers.txtдемонстрирует, что он работает на stdin так же хорошо, как cat numbers.txt |и на. И это не учит плохим привычкам.
Сюн Чямов

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

Или вы можете ввести числа в командной строке:

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

Тем не менее, этот файл прихлебывает файл, поэтому не рекомендуется использовать его для больших файлов. Смотрите ответ j_random_hacker, который избегает лгать.


3

Следующее должно работать (при условии, что ваш номер является вторым полем в каждой строке).

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

2
Вам действительно не нужна часть {sum = 0}
Uphill_ What '1

3

Однострочник в ракетке:

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

3

C (не упрощено)

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)

Я должен был поддержать комментарий. Нет ничего плохого в ответе - это довольно хорошо. Однако, чтобы показать, что комментарий делает ответ потрясающим, я просто добавляю комментарий.
bballdave025

3

Заранее извиняюсь за удобочитаемость обратных кавычек ("` "), но они работают в оболочках, отличных от bash, и, следовательно, более удобны для вставки. Если вы используете оболочку, которая принимает его, формат $ (command ...) будет гораздо более читабельным (и, следовательно, отлаживаемым), чем `command ...`, поэтому не стесняйтесь изменять для вашего здравого смысла.

У меня есть простая функция в моем bashrc, которая будет использовать awk для вычисления количества простых математических элементов

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

Это будет делать +, -, *, /, ^,%, 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
}

2
Какую древнюю оболочку вы используете, которая не поддерживает $()?
nyuszika7h
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.