Averaging lines without awk using bash

I'm just starting to use bash, and I'm having some difficulties with bash arithmetic. Lets say I have a some cities with three temperatures, and I'd like to average the temperatures without using the awk command. How can I do this? I've done it with awk, but I'm practicing with different techniques.

# cityTemp.txt with four cities, three day's temperatures
Toronto 20 25 30
Miami 80 80 110
London 40 20 60
New York 5 10 15 

Lets say I'm trying to write a script to output:

25 Toronto
90 Miami
40 London
10 New York

I've already done this with some piping and the awk command, but I'm having problems doing this without using awk.

Again, I'm new to this. I tried a for loop, but I didn't really know what I was doing.

---edit 1:

Benjamin W. I'm editing again, but right now I'm playing with a loop:

#!/bin/bash

for i in $(cat $1)
do
    echo "i is: ${i}"
done < $1

This is, maybe obviously, just printing every field from cityTemp.txt one at a time.

---edit 2:

This was my ending attempt

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`
    Output = ${average}
done < $1
echo ${averages}

Answers


This should work in any Posix shell, so we may as well shebang it with #!/bin/sh.

#!/bin/sh

avg () {
  city=
  while [ $# -gt 3 ]; do
    city="$city $1"
    shift
  done
  echo $((($1 + $2 + $3) / 3)) $city
}

while read line; do
  avg $line
done < cityTemp.txt

This would have been a lot easier if the city was last on each line, lol.


this should work for a file with an arbitrary nymber of lines and 3 teampreature inputs:

#!/bin/bash
i=1
file=file
nolines=`wc -l $file | cut -d' ' -f1`
while [ $i -lt $nolines ]
do
line=$(sed -n "$i"p $file)
city=$(echo $line | cut -d' ' -f1)
val1=$(echo $line | cut -d' ' -f2)
val2=$(echo $line | cut -d' ' -f3)
val3=$(echo $line | cut -d' ' -f4)
sum=$(($val1 + $val2 + $val3))
avg=$(($sum / 3))
echo $avg $city
i=$[i+1]
done

This takes advantage of a few Bash features, but ended up rather bulky:

#!/bin/bash                                                                                                                         

# Regex to capture city name and temperatures                                                                                       
re='([[:alpha:] ]*) ([[:digit:] ]*)'                                                                                                

while IFS= read -r line; do                                                                                                         
    unset temps                             # Delete array                                                                                                  
    [[ $line =~ $re ]]                      # Capture city name and temperatures
    city="${BASH_REMATCH[1]}"               # Assign city name                                                                              
    read -a temps <<< "${BASH_REMATCH[2]}"  # Assign temperatures to array                                                          

    sum=0                                                                                                                           

    # Loop over temperatures                                                                                                        
    for temp in "${temps[@]}"; do                                                                                                   
        (( sum += temp ))                                                                                                           
    done                                                                                                                            

    # Print average                                                                                                                 
    printf "%d %s\n" $(( sum / ${#temps[@]} )) "$city"                                                                              
done < "$1"

It would be much simpler if the city name always consisted of the same amount of words, but there is "New York", so I use a regex to separate name and temperatures.

The temperatures are then assigned to an array with read -a, over which I loop to get the total.

Assigning to the array to loop over could be replaced by directly looping over the string and relying on word splitting:

for temp in ${BASH_REMATCH[2]}; do

but then we'd have to keep track of the number of temperature values by updating a counter or something similar.


Here is a version that uses Bash's builtin regex to add the arbitrary elements after the city names.

Given:

$ echo "$nums"
Toronto 20 25 30
Miami 80 80 110
London 40 20 60
New York 5 10 15

You can do:

regex="^([a-zA-Z ]+)(.+)$"
echo "$nums" | while read line
do
    [[ $line =~ $regex ]]
    name="${BASH_REMATCH[1]}"
    arr=(${BASH_REMATCH[2]})
    sum=0
    for i in ${arr[@]}; do
        let sum+=$i
    done
    printf "%s %s\n" $(( $sum / ${#arr[@]} )) "$name"
done

Prints:

25 Toronto 
90 Miami 
40 London 
10 New York 

If you want floating point, you can use bc like so.

Given:

$ echo "$nums"
Toronto 22 25 30
Miami 80 80 110
London 40 20 60
New York 5 10 15

You can do:

echo "$nums" | while read line
do
    [[ $line =~ $regex ]]
    name="${BASH_REMATCH[1]}"
    arr=(${BASH_REMATCH[2]})
    avg=$(IFS='+'; echo "scale=2;(${arr[*]})/${#arr[@]}" | bc -l)
    printf "%s %s\n" $avg "$name"   
done

Prints:

25.66 Toronto 
90.00 Miami 
40.00 London 
10.00 New York 

Need Your Help

Android: How to check condition with async task return value

android android-asynctask

I am doing some processing in async task to check some values on server if the value present server php script returns true otherwise false.

Webpack compiles but webpack-dev-server does not with same config

javascript webpack vue.js

I'm beginning to learn webpack and trying to work through using the webpack-dev-server for compiling and browser reloading.