2

I want to loaded a temporary files with the bunch of numbers from input file. The format of the script is "testing -row/-col [input file]". The input file mostly just a bunch of random numbers like

1 2 3 4
3 3 5 6
9 4 4 2

My code below is trying to grab this input file as argument and then "cat" these numbers into a new temporary files. From there, I'm trying to find average value of the row from this temporary files.

FILENAME=$2
TMP=./TMP2.$$
cat $FILENAME > $TMP

#average row 
function avg_row {
while read -a row  
do
    total=0
    sum=0
    for i in "${rows[@]}"
    do
         eum=`expr $sum + $i`
         total=`expr $total + 1`
    done
    average=`expr $sum / $total`
    echo $average
done < $TMP
}

However, even though when I "cat" the TMP files it display exactly the same like testing_file, when I run the script it prints

 expr: division by zero
 expr: division by zero
 expr: division by zero

Any suggestions or idea on why this could happen? Thank you.

Cyrus
  • 84,225
  • 14
  • 89
  • 153
John
  • 129
  • 9
  • Isn't it by saying "cat $FILENAME > $TMP", it automatically read the entire file? I'm new with bash, so sorry if I'm wrong. What's the proper way then? – John Oct 07 '15 at 05:38
  • No, that's just an implementation of `cp "$FILENAME" "$TMP"`. It has nothing to do with reading the file. Just use `$FILENAME` where you are using `$TMP` in your loop. – chepner Oct 07 '15 at 12:38

1 Answers1

1

Two issues:

  1. Your function reads in array row but then attempts to access a nonexistent array rows. Because rows has no entries, total is never incremented and the division is division-by-zero.

  2. This line updates eum, not sum:

    eum=`expr $sum + $i`
    

Also, it is not clear why the contents of $FILENAME are copied before they are read. I will assume that you have a good reason for this.

A corrected function looks like:

function avg_row {
while read -a row  
do
    total=0
    sum=0
    for i in "${row[@]}"
    do
         sum=`expr $sum + $i`
         total=`expr $total + 1`
    done
    average=`expr $sum / $total`
    echo $average
done < $TMP

This produces the output:

$ avg_row
2
4
4

Modernized bash version

Both backticks and expr are archaic. A more modern bash version of the function is:

avg_row2() {
while read -a row
do
    sum=0
    for i in "${row[@]}"
    do
        ((sum += i))
    done
    echo $((sum / ${#row[@]}))
done < $TMP
}

This produces the same output as before:

$ avg_row2
2
4
4

awk version

The same thing can be accomplished in awk:

$ awk '{s=0; for (i=1;i<=NF;i++) s+=$i; print int(s/NF);}' filename
2
4
4

Unlike bash, awk can do floating point arithmetic:

$ awk '{s=0; for (i=1;i<=NF;i++) s+=$i; print s/NF;}' filename
2.5
4.25
4.75
John1024
  • 109,961
  • 14
  • 137
  • 171
  • Wow, yeah my bad that I didn't scan my code carefully. I was too confused with the calling temporary files part. Thanks for your help! – John Oct 07 '15 at 06:41
  • Gross hack: `sum=$(IFS='+'; echo $(( ${nums[*]} )) )`. – chepner Oct 07 '15 at 15:22