3

I have a line of numbers, and I want to be able to process them one by one. For ex.

3829 4837 3729 2874 3827 

I want to be able to get the sum, largest number, anything. But I'm not sure how to get each number to process it. I would REAAAAALLLLLLLLY like to stick with pure bash, I don't mind a convoluted solution in bash. But if awk is absolutely necessary I will use it. I don't want to use sed.

I think I could do something like:

max= cut -d" " -f1
in line L cut -d" " -f$(loop index) 
#check for max

I'm confused.

Edit: Thanks for your answers, I've seen some new things I've never seen in bash and I'm ready to explore them more. I received the info I sought and even more! :)

BenMorel
  • 34,448
  • 50
  • 182
  • 322

3 Answers3

3

If you want to process process numbers one by one, taking advantage of shell word splitting :

numbers="3829 4837 3729 2874 3827"
 for n in $numbers; do
     # do something with "$n"
done

SUM :

numbers="3829 4837 3729 2874 3827"
echo $((${numbers// /+}))

or

numbers="3829 4837 3729 2874 3827"
for n in $numbers; do
     ((sum+=n))
done

echo $sum

LARGEST :

numbers="3829 4837 3729 2874 3827"
for n in $numbers; do
     ((max<n)) && max=$n
done

echo $max

Alternatively, if you want a global SUM with some shell tricks :

$ tr ' ' '+' <<< '3829 4837 3729 2874 3827' | bc                                 
19096 

or

$ awk '{$1=$1; print}' OFS=+ <<< '3829 4837 3729 2874 3827' | bc
19096

or

$ echo  '3829 4837 3729 2874 3827' |
    awk '{for (i=1; i<=NF; i++) c+=$i} {print c}'
19096
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
3

If you have a list of numbers in a string, processing them as follows is the safest way to split and iterate (while avoiding glob expansion or other side effects):

max=0
read -r -a number_array <<<"$numbers"
for number in "${number_array[@]}"; do
  if (( number > max )) ; then
    max=number
  fi
done    

If you were reading from a file, a simple modification applies (see http://mywiki.wooledge.org/BashFAQ/001 for best practices on reading files):

max=0
while read -r -a numbers; do
  for number in "${numbers[@]}"; do
    if (( number > max )) ; then
      max=number
    fi
  done
done <input-file

If your delimiters are something else, you can set IFS appropriately. For instance, if delimited by commas:

while IFS=, read -r -a numbers; do

Note that the best practices given here don't matter as much when your values really are restricted to only ever be numbers. If you had a * in your input data, however, and you simply ran

for number in $numbers; do

then the * in the $numbers string would be replaced with a list of files in the current directory. Don't do that.

One final note -- bash has no built-in support for floating-point math. If you need floating-point math, see http://mywiki.wooledge.org/BashFAQ/022.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • No need `while IFS=, read -r` for a list of integers. Read again the title =) – Gilles Quénot Mar 21 '13 at 19:17
  • @sputnick Depends on how the integers are delimited. If they're separated by commas, `IFS=,` certainly is needed. I _did_ point out in the text that the `IFS=,` was only needed in that case; part of the stated point of SO is to have a repository of knowledge that can be used by other people, as opposed to answering only the direct and immediate question. – Charles Duffy Mar 21 '13 at 19:18
  • And another thing, where did you read something about a "file" in OP POST ? I just see "line", so a string. – Gilles Quénot Mar 21 '13 at 19:32
  • @sputnick Fair enough. Amended to start with a simpler, non-file-based answer. – Charles Duffy Mar 22 '13 at 00:07
  • Thanks!!! I am in fact reading from a file, but the processing part I lost myself in overthinking. Thanks also for the math tip! P.S. Yesterday, I printed out the bash guide from the wooledge source. I'm headed in the right direction, I see. – Khadijah Celestine Mar 22 '13 at 03:51
2

Here's a couple of ways to split a string of words into a structure you can iterate over:

use the positional parameters

numbers="3829 4837 3729 2874 3827"
set -- $numbers
sum=0
for n in "$@"; do
    # do something with $n
    (( sum += n ))
done
average=$(( sum / $# ))   # integer division only

use an actual array

numbers="3829 4837 3729 2874 3827"
nums=( $numbers )
sum=0
for n in "${nums[@]}"; do
    # do something with $n
    (( sum += n ))
done
average=$(( sum / ${#nums[@]} ))   # integer division only
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Thanks!!!! Does the $@ mean list of items? In using set -- $numbers are you declaring $@ = $numbers? Thanks for pointing out positional parameters. I'm learning more than I expected to. :) – Khadijah Celestine Mar 22 '13 at 04:02