Search code examples
bashexpr

Read, Store and Access Temporary Files (BASH)


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.


Solution

  • 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