URL: https://www.opennet.me/cgi-bin/openforum/vsluhboard.cgi
Форум: vsluhforumID9
Нить номер: 8117
[ Назад ]

Исходное сообщение
"Сложить массив чисел на bash"

Отправлено Stone , 27-Фев-09 15:06 
Есть файл вида

$cat total.bytes
1962647280
154580283
85368270
256233601
2448176455
3211021168
392011779
.........

т.е просто числа на каждой строке
нужно просуммировать их все - чтоб на выходе было одно число.
Я не нашел какой-нибудь встроенной функции для подсчета такого массива, и написал скрипт:

#!/bin/sh
cat total.bytes | \
while read line;
do
let y=y+line;
done
echo $y


скрипт выводит

$./add.sh
1962647280
2117227563
-2092371463
-1836137862
311345785
-1836137864
-1444126085
-710494993
1436988654
-1190184973

Т.е первые два числа сложил, а потом почему-то минусы какие-то появляются
что делаю не так?


Содержание

Сообщения в этом обсуждении
"Сложить массив чисел на bash"
Отправлено vic , 27-Фев-09 15:25 
>Есть файл вида
>
>$cat total.bytes
>1962647280
>154580283
>85368270
>256233601
>2448176455
>3211021168
>392011779

в минус уходит из-за переполнения int.

попробуйте так:
sum = 0; cat total.bytes | while read num ; do sum=`echo $sum+$num" | bc -q` ; done ; echo $sum


"Сложить массив чисел на bash"
Отправлено vic , 27-Фев-09 15:45 
а еще лучше так
result=`sum=0 ; (while read num; do sum=$(echo "$sum+$num" | bc -q); done ; echo $sum) < total.bytes`
echo $result


"без цикла"
Отправлено Andrey Mitrofanov , 27-Фев-09 17:01 
$ cat total.bytes
1962647280
154580283
85368270
256233601

2448176455
3211021168
392011779


$ ( tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'; echo ) |bc
8510038836
$ tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'|xargs|bc
8510038836
$ egrep -o '[0-9]+' <total.bytes |tr "\n" "+" |sed 's!+$!\n!'|bc
8510038836
$ _


"без цикла"
Отправлено Stone , 27-Фев-09 17:13 
>[оверквотинг удален]
>392011779
>
>
>$ ( tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'; echo ) |bc
>8510038836
>$ tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'|xargs|bc
>8510038836
>$ egrep -o '[0-9]+' <total.bytes |tr "\n" "+" |sed 's!+$!\n!'|bc
>8510038836
>$ _

То ли я что-то не то делаю, но у меня на все это syntax error выдает


"без цикла"
Отправлено vic , 27-Фев-09 17:14 
>[оверквотинг удален]
>392011779
>
>
>$ ( tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'; echo ) |bc
>8510038836
>$ tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'|xargs|bc
>8510038836
>$ egrep -o '[0-9]+' <total.bytes |tr "\n" "+" |sed 's!+$!\n!'|bc
>8510038836
>$ _

как бы bc не отвалился, хз сколько там чисел
echo `cat total.bytes` | tr " " "+" | bc
это все хорошо, а если там чисел столько что bc отвалится


"без цикла"
Отправлено Stone , 27-Фев-09 17:20 

>как бы bc не отвалился, хз сколько там чисел
>echo `cat total.bytes` | tr " " "+" | bc
>это все хорошо, а если там чисел столько что bc отвалится

Во, самый короткий вариант, и работает в баше и в sh!!!


"без цикла"
Отправлено vic , 27-Фев-09 17:21 
>[оверквотинг удален]
>>8510038836
>>$ tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'|xargs|bc
>>8510038836
>>$ egrep -o '[0-9]+' <total.bytes |tr "\n" "+" |sed 's!+$!\n!'|bc
>>8510038836
>>$ _
>
>как бы bc не отвалился, хз сколько там чисел
>echo `cat total.bytes` | tr " " "+" | bc
>это все хорошо, а если там чисел столько что bc отвалится

какая настойчивая мысль....)))))))))


"с бенчмарками :)"
Отправлено Andrey Mitrofanov , 02-Мрт-09 19:56 
>>$ ( tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'; echo ) |bc
>>$ tr "\n" "+" <total.bytes |sed 's!+\+!+!g;s!^+\|+$!!g'|xargs|bc
>>$ egrep -o '[0-9]+' <total.bytes |tr "\n" "+" |sed 's!+$!\n!'|bc
>как бы bc не отвалился, хз сколько там чисел

Действительно! %)

>echo `cat total.bytes` | tr " " "+" | bc
>это все хорошо, а если там чисел столько что bc отвалится

$ cat ./x41
#!/bin/bash

n=10
for((i=1;i<7;i++)); do
yes | head -$n |sed 's!y!1000000001!' | (\
/usr/bin/time bc <(echo 'sum=0;while((val=read())!=0){sum+=val}; sum') |\

tr -d "\n"|wc -c

) 2>&1 |tr "\n" " "|awk '{print '"$((${#n}-1))"'" "$NF" "$1" "$3" "$4;exit}'
n="${n}0"
done


#
#done$ ./x41
1 11 0.00user 0:00.00elapsed 0%CPU
2 12 0.00user 0:00.00elapsed 44%CPU
3 13 0.01user 0:00.01elapsed 70%CPU
4 14 0.09user 0:00.10elapsed 85%CPU
5 15 0.88user 0:01.06elapsed 83%CPU
6 16 8.82user 0:10.20elapsed 86%CPU
$ _

Вот такой вариант, соответственно, не подавился суммированием миллиона (десяти лениво было ждать) чисел:

bc <(echo 'sum=0;while((val=read())!=0){sum+=val}; sum')

Из трёх моих первых до таких "высот" не дожил ни один: xargs умер раньше всех, а при передаче сотни тысяч(или типа того) слагаемых в виде строки 1+1+1+,,, bc слишком активно кушал память (типа, всю свободную и начинал "свопиться" = тормозить себя и всю систему).

PS: <(echo '...') - башизм, чтоб сэкономить "лишний" файл.

PPS: Первая колонка "бенчмарка" - log($числа_слагаемых), вторая - "длинна" суммы.


"и, кстати, 'больше' не работает :/"
Отправлено Andrey Mitrofanov , 28-Май-09 18:22 
>bc <(echo 'sum=0;while((val=read())!=0){sum+=val}; sum')

Обновился с Debian 4.0 "Etch" на Debian 5.0 "Lenny" (bc 1.06-20 -> 1.06.94-3, bash 3.1dfsg-8 -> 3.2-4 и проч.) и это заклинание больше не работает. "=read())!=0" в нём костыль, конечно, по сравнению с "while read val; do" в bash-е: конец файла от числа 0 не отличает. А в Lenny и вообще не работает...


"Сложить массив чисел на bash"
Отправлено Stone , 27-Фев-09 15:47 
>[оверквотинг удален]
>>256233601
>>2448176455
>>3211021168
>>392011779
>
>в минус уходит из-за переполнения int.
>
>попробуйте так:
>sum = 0; cat total.bytes | while read num ; do sum=`echo
>$sum+$num" | bc -q` ; done ; echo $sum

Тоже подумал что от переполнения, но когда сложил вручную несколько первых чисел, но все нормально

Ваш скрипт к сожалению не работает, там вроде кавычки забыли, я поставил, все равно не пашет


"Сложить массив чисел на bash"
Отправлено Stone , 27-Фев-09 15:51 
>[оверквотинг удален]
>>
>>попробуйте так:
>>sum = 0; cat total.bytes | while read num ; do sum=`echo
>>$sum+$num" | bc -q` ; done ; echo $sum
>
>Тоже подумал что от переполнения, но когда сложил вручную несколько первых чисел,
>но все нормально
>
>Ваш скрипт к сожалению не работает, там вроде кавычки забыли, я поставил,
>все равно не пашет

А вот второй скрипт пашет, спасибо!


"Сложить массив чисел на bash"
Отправлено allez , 27-Фев-09 15:56 
>#!/bin/sh
>cat total.bytes | \
>while read line;
>do
>let y=y+line;
>done
>echo $y

Обратите особое внимание на строку с "let", она у вас не может работать.


$ cat total.bytes
1962647280
154580283
85368270
256233601
2448176455
3211021168
392011779

$ cat script.sh
#!/bin/sh
while read line;
do
  let y=$y+$line;
done < total.bytes
echo $y

$ ./script.sh
8510038836



"Сложить массив чисел на bash"
Отправлено Stone , 27-Фев-09 16:11 
>[оверквотинг удален]
>while read line;
>do
>  let y=$y+$line;
>done < total.bytes
>echo $y
>
>$ ./script.sh
>8510038836
>
>

Так тоже минусы выводит у меня!


"Сложить массив чисел на bash"
Отправлено vic , 27-Фев-09 16:39 
>[оверквотинг удален]
>>  let y=$y+$line;
>>done < total.bytes
>>echo $y
>>
>>$ ./script.sh
>>8510038836
>>
>>
>
>Так тоже минусы выводит у меня!

А вы cat | while делаете или while .. done < file ?
В первом случае может неявно вызываться субшелл и как следствие переменная y может быть не видна т.к ее область видимости - субшелл, отсюда глюки.

Что за система? (uname -a && sh --version) ?


"Сложить массив чисел на bash"
Отправлено Stone , 27-Фев-09 16:46 

>А вы cat | while делаете или while .. done < file
>?
>В первом случае может неявно вызываться субшелл и как следствие переменная y
>может быть не видна т.к ее область видимости - субшелл, отсюда
>глюки.
>
>Что за система? (uname -a && sh --version) ?

$ cat xyz.sh
#!/bin/sh
while read line;
do
  let m=$m+$line;
done < total.bytes.back
echo $m

$ ./xyz.sh
1962647280
2117227563
-2092371463
-1836137862
311345785
-1836137864
-1444126085
-1444126085

$ uname -a
FreeBSD  7.0-RELEASE FreeBSD 7.0-RELEASE #0: Fri Apr 11 20:30:15 MSD 2008    

$ sh --version
Illegal option --


"Сложить массив чисел на bash"
Отправлено Stone , 27-Фев-09 16:47 
О, а если bash поставить, то работает!Дело в sh значит

$ cat xyz.sh
#!/usr/local/bin/bash
while read line;
do
  let m=$m+$line;
done < total.bytes.back
echo $m
$ ./xyz.sh
8510038836


"Сложить массив чисел на bash"
Отправлено vic , 27-Фев-09 17:04 
>О, а если bash поставить, то работает!Дело в sh значит

однако суровый sh в freebsd :))

result=`y=0; (while read line; do let y=$y+$line; done ; echo $y) < total.bytes`
echo $result

что буит?



"Сложить массив чисел на bash"
Отправлено Stone , 27-Фев-09 17:09 
>>О, а если bash поставить, то работает!Дело в sh значит
>
>однако суровый sh в freebsd :))
>
>result=`y=0; (while read line; do let y=$y+$line; done ; echo $y) <
>total.bytes`
>echo $result
>
>что буит?

$ ./xyz.sh
1962647280 2117227563 -2092371463 -1836137862 311345785 -1836137864 -1444126085 -710494993 1436988654 -1190184973 957298674 -1190184975 338450189 623557529 790395966 924888457 -1222595192 138906228 1676938475 1857929764 1857956963 -765632036 1316748116 -1132369225 1015114422 1520280995 1520280995

Тож самое, минусы.А как узнать версию sh?и его вообще можно обновить, в портах нету его


"Сложить массив чисел на bash"
Отправлено vic , 27-Фев-09 17:19 
>Тож самое, минусы.А как узнать версию sh?и его вообще можно обновить, в
>портах нету его

уже можно не парится, итак видно что суровый :)
а минусы это переполнение (ибо сурово), поэтому во втором варианте я и предложил bc :)


"Сложить массив чисел на bash"
Отправлено 0dmin , 03-Мрт-09 23:00 
>>О, а если bash поставить, то работает!Дело в sh значит
>однако суровый sh в freebsd :))

Номальный POSIX Shell.

PS: А да - во фряхе _внезапно!_ bash зовётся bash'ем а не sh :)


"Сложить массив чисел на bash"
Отправлено andreik , 03-Мрт-09 16:31 
awk 'BEGIN {c=0} {c=c+$0} END {printf("%.0f\n", c)}' total.bytes

"Сложить массив чисел на bash"
Отправлено Andrey Mitrofanov , 03-Мрт-09 18:04 
>awk 'BEGIN {c=0} {c=c+$0} END {printf("%.0f\n", c)}' total.bytes

Если точности плавающих - хватает, то да, так быстрее:

$ cat ./x5
#!/bin/bash

n=10
for((i=1;i<8;i++)); do
yes | head -$n |sed 's!y!18446744073799!' | (\
/usr/bin/time gawk 'BEGIN {c=0} {c=c+$0} END {printf("%.0f\n", c)}' |\
tr -d "\n"
#|wc -c

) 2>&1 |tr "\n" " "|awk '{print '"$((${#n}-1))"'" "$NF" "$1" "$3" "$4;exit}'
n="${n}0"
done
$ ./x5
1 184467440737990 0.00user 0:00.00elapsed 0%CPU
2 1844674407379900 0.00user 0:00.00elapsed 0%CPU
3 18446744073799512 0.00user 0:00.00elapsed 0%CPU
4 184467440737950784 0.00user 0:00.02elapsed 60%CPU
5 1844674407378830848 0.10user 0:00.22elapsed 47%CPU
6 18446744074118029312 0.94user 0:02.66elapsed 36%CPU
7 184467440723846004736 9.78user 0:24.68elapsed 40%CPU
# _


То же самое с s/gawk/mawk/:

$ ./x5
1 184467440737990 0.00user 0:00.00elapsed 0%CPU
2 1844674407379900 0.00user 0:00.00elapsed 133%CPU
3 18446744073799512 0.00user 0:00.00elapsed 0%CPU
4 184467440737950784 0.00user 0:00.01elapsed 26%CPU
5 1844674407378830848 0.06user 0:00.17elapsed 38%CPU
6 18446744074118029312 0.51user 0:01.71elapsed 31%CPU
7 184467440723846004736 5.28user 0:19.83elapsed 27%CPU


"Сложить массив чисел на bash"
Отправлено vic , 03-Мрт-09 18:12 
у меня и %d работает в этом решении

"Сложить массив чисел на bash"
Отправлено krigs , 25-Май-09 03:31 
>[оверквотинг удален]
>-1836137862
>311345785
>-1836137864
>-1444126085
>-710494993
>1436988654
>-1190184973
>
>Т.е первые два числа сложил, а потом почему-то минусы какие-то появляются
>что делаю не так?

Только что наткнулся на книгу Евгения Миньковского (http://house.hcn-strela.ru/BSDCert/BSDA-course/index.html) - там и почерпнул идею :)

$ cat count
#!/bin/sh                                                    
sum=0                          
for num in `cat ./total.bytes`; do  
    sum=`echo $sum+$num | bc`
done                          
echo $sum
$ ./count
8510038836
Просто и эффектно. Кстати этот скрипт также будет обрабатывать числа, даже если они введены в файле через пробелы или табуляцию. А программа bc работает и с числами с десятичной точкой.


"Сложить массив чисел на bash"
Отправлено Andrew , 29-Май-09 23:57 
>[оверквотинг удален]
>sum=0
>for num in `cat ./total.bytes`; do
>    sum=`echo $sum+$num | bc`
>done
>echo $sum
>$ ./count
>8510038836
>Просто и эффектно. Кстати этот скрипт также будет обрабатывать числа, даже если
>они введены в файле через пробелы или табуляцию. А программа bc
>работает и с числами с десятичной точкой.

Блин.... только хотел написать про bc, оказалось уже в цитате написано. А если по существу, если вы покурите man bc более вдумчиво, решение выйдет еще более элегантное.


"Сложить массив чисел на bash"
Отправлено anonymous , 30-Май-09 06:01 
>Просто и эффектно.

Ни разу не эффективно на каждое сложение запускать новый процесс bc.


"Сложить массив чисел на bash"
Отправлено angra , 31-Май-09 08:28 
Использовать bash для этой задачи вообще не эффективно, тем более, что в конечном итоге опять таки используется bc, так почему просто не привести файл в удобный для bc вид. Но если хочется:
for s in 1;do for i in `cat text` ;do echo -n "$i"+;done;echo 0;done |bc

"а сертифицированные BSDA читают книги"
Отправлено Andrey Mitrofanov , 01-Июн-09 17:25 
>Использовать bash для этой задачи вообще не эффективно, тем более, что в
>конечном итоге опять таки используется bc, так почему просто не привести
>файл в удобный для bc вид. Но если хочется:
>for s in 1;do for i in `cat text` ;do echo -n
>"$i"+;done;echo 0;done |bc

|*) for не нужен. back-ticks не нужен (больше ~130к файла не влезет).
Всё "как положено": awk быстрее, на bc точность не страдает...

$ cat ./x6
#!/bin/bash

n=10
for((i=1;i<8;i++)); do
yes | head -$n |sed 's!y!10000000000001!' | (\
{ echo "sum=0"; sed 's/.\+/sum+=\0/'; echo 'print sum," "'; } | /usr/bin/time bc

) 2>&1 |awk '{print '"$((${#n}-1))"'" "$1" "$2" "$4" "$5;exit}'
n="${n}0"
done
$ ./x6
1 100000000000010 0.00user 0:00.00elapsed 0%CPU
2 1000000000000100 0.00user 0:00.00elapsed 200%CPU
3 10000000000001000 0.00user 0:00.00elapsed 100%CPU
4 100000000000010000 0.03user 0:00.05elapsed 61%CPU
5 1000000000000100000 0.36user 0:00.63elapsed 57%CPU
6 10000000000001000000 3.86user 0:06.94elapsed 56%CPU
7 100000000000010000000 39.76user 1:12.61elapsed 55%CPU
$ cat ./x6a
#!/bin/bash

n=10
for((i=1;i<8;i++)); do
yes | head -$n |sed 's!y!10000000000001!' | (\
/usr/bin/time mawk 'BEGIN {c=0} {c=c+$0} END {printf("%.0f ", c)}'

) 2>&1 |awk '{print '"$((${#n}-1))"'" "$1" "$2" "$4" "$5;exit}'
n="${n}0"
done
$ ./x6a
1 100000000000010 0.00user 0:00.00elapsed 0%CPU
2 1000000000000100 0.00user 0:00.00elapsed 100%CPU
3 10000000000000900 0.00user 0:00.00elapsed 57%CPU
4 100000000000000912 0.00user 0:00.01elapsed 53%CPU
5 1000000000000001024 0.05user 0:00.15elapsed 38%CPU
6 10000000000000002048 0.48user 0:01.56elapsed 32%CPU
7 99999999978526310400 5.17user 0:15.50elapsed 34%CPU
$ _


"а сертифицированные BSDA читают книги"
Отправлено angra , 02-Июн-09 00:52 
>|*) for не нужен. back-ticks не нужен (больше ~130к файла не влезет).

Андрей, неужели ты думаешь, что я делаю так как написал. Была мысль показать while read с перенаправлением, но ведь объяснять бы пришлось. Задача была показать в простом виде как избавится от множественных вызовов bc(или любой другой программы) в подобных случаях, как следствие внес минимум изменений в код из книжки(про умственную отсталость автора книги отдельный разговор).
Мое отношение к башу в такой задаче я в любом случае выразил однозначно.