Search code examples
arraysbashexpr

bash- find average of numbers in line


I am trying to read a file line by line and find the average of the numbers in each line. I am getting the error: expr: non-numeric argument

I have narrowed the problem down to sum=expr $sum + $i, but I'm not sure why the code doesn't work.

while read -a rows
do
    for i in "${rows[@]}"
    do
        sum=`expr $sum + $i`
        total=`expr $total + 1`
    done
    average=`expr $sum / $total`
done < $fileName

The file looks like this (the numbers are separated by tabs):

1       1       1       1       1
9       3       4       5       5
6       7       8       9       7
3       6       8       9       1
3       4       2       1       4
6       4       4       7       7

Solution

  • With some minor corrections, your code runs well:

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

    With the sample input file, the output produced is:

    1
    5
    7
    5
    2
    5
    

    Note that the answers are what they are because expr only does integer arithmetic.

    Using sed to preprocess for expr

    The above code could be rewritten as:

    $ while read row; do expr '(' $(sed 's/  */ + /g' <<<"$row") ')' / $(wc -w<<<$row); done < filename
    1
    5
    7
    5
    2
    5
    

    Using bash's builtin arithmetic capability

    expr is archaic. In modern bash:

    while read -a rows
    do
        total=0
        sum=0
        for i in "${rows[@]}"
        do
            ((sum += $i))
            ((total++))
        done
        echo $((sum/total))
    done <filename
    

    Using awk for floating point math

    Because awk does floating point math, it can provide more accurate results:

    $ awk '{s=0; for (i=1;i<=NF;i++)s+=$i; print s/NF;}' filename
    1
    5.2
    7.4
    5.4
    2.8
    5.6