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


867

Я шукаю команду, яка прийме (як введення) кілька рядків тексту, кожен рядок містить одне ціле число, і виведе суму цих цілих чисел.

Як невеликий фон, у мене є файл журналу, який включає вимірювання часу. Через привітання до відповідних рядків і трохи sedпереформатування я можу перерахувати всі таймінги у цьому файлі. Я хотів би опрацювати загальне. Я можу передавати цей проміжний висновок будь-якій команді, щоб зробити остаточну суму. Я завжди використовував exprу минулому, але якщо він не працює в режимі RPN, я не думаю, що він впорається з цим (і навіть тоді це було б складно).

Як я можу отримати підсумовування цілих чисел?


2
Це дуже схоже на питання , я запитав деякий час назад: stackoverflow.com/questions/295781 / ...
AndreW

5
Мені дуже подобається це питання за те, що існує маса можливих правильних (або принаймні робочих) відповідей.
Франциско Канедо

Це питання є проблемою для коду гольфу. codegolf.stackexchange.com :)
Гордон Бін

Відповіді:


1322

Трохи awk повинен це зробити?

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

Крім того, коли трубопроводи від stdin,

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

1
Дуже хороший! Я поставив би пробіл перед "+", просто щоб допомогти мені краще розібратися, але це було дуже зручно для прошивки деяких номерів пам'яті через paste & потім bc.
Майкл Х.

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

19
У мене файл зі 100 мільйонами номерів. Команда awk займає 21 секунду; команда вставити займає 41 сек. Але приємно зустріти "вставити" все-таки!
Абхі

4
@Abhi: Цікаво: DI здогадуюсь, мені знадобиться 20 років, щоб розібратися з командою awk, тому вона вирівнюється, хоча поки я не спробую 100 мільйонів і одне число: D
Марк К Коуан

6
@George Ви можете залишити поза -, хоча. (Корисно, якщо ви хочете поєднати файл зі stdin).
Алоїз Магдал

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))
Гранітозавр

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 відбулося раніше, тому що підсумовуючий код той самий.
Роберт Клемме

9
Бо проблема насправді полягає у функції «друк». Awk використовує 64 бітові цілі числа, але чомусь друкує донорські масштаби їх на 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.
ikrabbe

5
Онлайн алгоритм: dc -e '0 0 [+?z1<m]dsmxp'. Тому ми не зберігаємо всі числа на стеці перед обробкою, але читаємо та обробляємо їх по одному (якщо бути точнішим, рядок за рядком, оскільки один рядок може містити кілька чисел). Зауважте, що порожній рядок може закінчити послідовність введення.
рувим

@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
Мені це подобається, тому що я думаю, що це настільки чітко і коротко, що я насправді зможу його запам'ятати.
Альфе

46

Чистий і короткий баш.

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

9
Це найкраще рішення, оскільки воно не створює жодного підпроцесу, якщо ви замінюєте перший рядок на f=$(<numbers.txt).
loentar

1
будь-який спосіб мати вхід від stdin? як з труби?
njzk2

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

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

2
Я майже втратив надію, коли досяг цього. Чистий баш!
Омер Ахтер

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

4
І я додав їх назад: "-l" гарантує, що вихід закінчується LF, як оболонка `` зворотних посилань і більшість програм очікує, і "<" вказує, що ця команда може бути використана в конвеєрі.
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 для конвеєра. Дуже просте і легке рішення для мене
Gelin Luo

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

Тому я здогадуюсь, що для 100 мільйонів чисел це знадобилося б ~ 35s.

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 (15s).


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

Далі: numba + паралелізація
gerrit


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}'

Ви можете використовувати цю команду або шляхом передачі списку чисел через стандартний вхід, або шляхом передачі файлу, що містить числа як параметр.



11

Наступні роботи в bash:

I=0

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

echo $I

1
Розширення команд слід використовувати обережно, коли файли можуть бути довільно великими. З числом.txt в 10 Мб цей cat numbers.txtкрок був би проблематичним.
Джакомо

1
Дійсно, однак, якби не кращі рішення, знайдені тут, я б користувався цим, поки я фактично не стикався з цією проблемою.
Франциско Канедо

11

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

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


Приклад: numsum numbers.txt.
agc

9

Я усвідомлюю, що це давнє питання, але мені це рішення подобається досить, щоб поділитися ним.

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

Якщо є інтерес, я поясню, як це працює.


10
Будь ласка, не варто. Нам подобається робити вигляд, що -n і -p - це приємні семантичні речі, а не просто якісь розумні обклеювання струн;)
Хоббс

2
Так, будь ласка, поясніть :) (я не хлопець Perl typea.)
Єнс

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

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 'карта {$ x + = $ _} <>; print $ x 'infile.txt
Avi

Пам'ять потрібна майже 2 Гб для великого введення 10 мільйонів чисел
Аміт Найду


5

Неможливо надіслати це:

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

Тут ви знайдете:
Найелегантніший одношаровий корпус Unix для підрахунку списку чисел довільної точності?

І ось його особливі переваги перед awk, bc та друзями:

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

Будь ласка, включіть у відповідь код, пов’язаний із запитанням, а не посилайтесь
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
кішка перенасичена, перенаправлення stdin з файлу: python -c "..." <numbers.txt
Giacomo

2
@rjack: catвикористовується для демонстрації того, що скрипт працює як для stdin, так і для файлів у argv [] (як while(<>)у Perl). Якщо ваш вхід у файлі, то значення <<'зайве.
jfs

2
Але < numbers.txtдемонструє, що він працює на stdin так само добре, як і cat numbers.txt |він. І це не вчить шкідливим звичкам.
Xiong Chiamiov

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

Вибачте заздалегідь за читабельність задніх планок ("` "), але вони працюють в оболонках, окрім башма, і, таким чином, є більш зручними. Якщо ви використовуєте оболонку, яка приймає її, формат $ (команда ...) набагато легше читається (і, таким чином, налагоджується), ніж "команда ...", тому не соромтесь змінювати для вашого розуму.

У моїй системі 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// /+}`

btw, якщо мені потрібно зробити щось швидке на робочому столі, я використовую це:

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.