2

I'm currently trying to write a function in my bash script that does the following: Take in a file as an argument and calculate the sum of the numbers within that file. I must make use of a for loop and the bc command.

Example of values in the file (each value on their own line):

12
4
53
19
6

So here's what I have so far:

function sum_values() {
  for line in $1; do
  #not sure how to sum the values using bc
  done
}

Am I on the right track? I'm not sure how to implement the bc command in this situation.

shinryu333
  • 333
  • 2
  • 14
  • 1
    See [BashFAQ #1](http://mywiki.wooledge.org/BashFAQ/001) for best practices re: processing a stream line-by-line. Also, note that whoever set your assignment was instructing you to use bad practices: [**`for` loops should not be used to iterate over line-oriented content.**](http://mywiki.wooledge.org/DontReadLinesWithFor) – Charles Duffy Dec 07 '17 at 00:04
  • 1
    ...well, not unless you read that line-oriented content into an array first; for example: `readarray -t values – Charles Duffy Dec 07 '17 at 00:06
  • 3
    ...and btw, using the `function` keyword is not particularly good form -- it makes your code incompatible with baseline POSIX implementations of `/bin/sh`, but -- unlike more useful bashisms -- adds no value in return for that incompatibility; POSIX function declaration is just `sum_values() {` with no preceding `function`. – Charles Duffy Dec 07 '17 at 00:12
  • 2
    @CharlesDuffy heh :), Don't forget than most teachers teaching `bash` will not understands the half of good `bash` answers in this site... so... – clt60 Dec 07 '17 at 00:19

4 Answers4

2

You are not on track. Why?

  • You are passing the whole file content as a variable which requires to store the whole file in memory. Not a problem with a 1, 2, 3 example, big no go in real life.
  • You are iterating over the content of a file using a for in loop assuming that you are iterating over the lines of that file. That is not true, because word splitting will be performed which makes the for in loop literally iterate over words, not lines.
  • As others mentioned, the shell is not the right tool for it. That's because such kind of processing is very slow with the shell compared to awk for example. Furthermore the shell is not able to perform floating point operations, meaning you can only process integers. Use awk.

Correct would be (with bash, for educational purposes):

# Expects a filename
sum() {
    filename=${1}
    s=0
    while read -r line ; do
        # Arithmetic expansion
        s=$((s+line))
        # Or with bc
        # s=$(bc <<< "${s}+${line}")
        # With floating point support
        # s=$(bc -l <<< "${s}+${line}")
    done < "${filename}"
    echo "${s}"
}

sum filename

With awk:

awk '{s+=$0}END{print s}' filename
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • 1
    Maybe point out that in addition to brevity, the Awk solution is better because it can cope with floating point, too. – tripleee Dec 07 '17 at 04:44
  • very good point! Also I forgot that the OP wanted to use `bc` for the calculation. Just woke up. Will change this later. – hek2mgl Dec 07 '17 at 05:38
2

You can do it easily without the need of a for loop.

paste -s -d+ numbers.txt | bc
edi_allen
  • 1,878
  • 1
  • 11
  • 8
0

While awk (or other higher level language: perl, python, etc) would be better suited for this task, you are on the right track for doing it the naive way. Tip:

$ x=1
$ y=$(bc <<<"$x + 1")
$ echo $y
2
bishop
  • 37,830
  • 11
  • 104
  • 139
0

To do math in bash we surround an operation in $(( ... ))

Here are some examples:

$(( 5 + 5 )) # 10

my_var = $((5 + 5)) # my_var is now 10

my_var = $(($my_var + 5)) # my_var is now 10

Solution to your problem:

function sum_values() {
    sum=0
    for i in $(<$1); do
        sum=$(($sum + $i))
    done
    echo $sum
}

Note that you could have also done $(cat $1) instead of $(<$1) in the solution above.

Edit: Replaced return $sum with echo $sum

Grayson Croom
  • 91
  • 1
  • 7