Is it possible to write a script that reads the file containing numbers (one per line) and writes their maximum, minimum and sum. If the file is empty, it will print an appropriate message. The name of the file is to be given as the parameter of the script. I mange to create below script, but there are 2 errors:

./4.3: line 20: syntax error near unexpected token `done'
./4.3: line 20: `done echo "Max: $max" '

Is it possible to add multiple files as parameter?

lines=`cat "$1" | wc -l` 
if [ $lines -eq 0 ]; 
then echo "File $1 is empty!" 
exit fi min=`cat "$1" | head -n 1` 
max=$min sum=0 
while [ $lines -gt 0 ]; 
do num=`cat "$1" | 
tail -n $lines` 
if [ $num -gt $max ]; 
then max=$num 
elif [ $num -lt $min ]; 
then min=$num fiS 
sum=$[ $sum + $num] lines=$[ $lines - 1 ] 
done echo "Max: $max" 
echo "Min: number $min"
echo "Sum: $sum"

5 Answers

0
Thomas Ulich On

You can do the whole thing in one while loop inside a shell script. Here's the bash version:

s=0
while read x; do
  if [ ! $mi ]; then
    mi=$x
  elif [ $mi -gt $x ]; then
    mi=$x
  fi
  if [ ! $ma ]; then
    ma=$x
  elif [ $ma -lt $x ]; then
    ma=$x
  fi
  s=$((s+x))
done
if [ ! $ma ]; then
  echo "File is empty."
else
  echo "s=$s, mi=$mi, ma=$ma"
fi

Save that script into a file, and then you can use pipes to send as many input files into it as you wish, like so (assuming the script is called "mysum"):

cat file1 file2 file3 | mysum

or for a single file

mysum < file1

(Make sure, the script is executable and on the $PATH, otherwise use "./mysum" for the script in the current directory or indeed "bash mysum" if it isn't executable.)

The script assumes that the numbers are one per line and that there's nothing else on the line. It gives a message if the input is empty.

How does it work? The "read x" will take input from stdin line-by-line. If the file is empty, the while loop will never be run, and thus variables mi and ma won't be set. So we use this at the end to trigger the appropriate message. Otherwise the loop checks first if the mi and ma variables exist. If they don't, they are initialised with the first x. Otherwise it is checked if the next x requires updating the mi and ma found thus far.

Note that this trick ensures that you can feed-in any sequence of numbers. Otherwise you have to initialise mi with something that's definitely too large and ma with something that's definitely too small - which works until you encounter a strange number list.

Note further, that this works for integers only. If you need to work with floats, then you need to use some other tool than the shell, e.g. awk.

Just for fun, here's the awk version, a one-liner, use as-is or in a script, and it will work with floats, too:

cat file1 file2 file3 | awk 'BEGIN{s=0}; {s+=$1; if(length(mi)==0)mi=$1; if(length(ma)==0)ma=$1; if(mi>$1)mi=$1; if(ma<$1)ma=$1} END{print s, mi, ma}'

or for one file:

awk 'BEGIN{s=0}; {s+=$1; if(length(mi)==0)mi=$1; if(length(ma)==0)ma=$1; if(mi>$1)mi=$1; if(ma<$1)ma=$1} END{print s, mi, ma}' < file1

Downside: if doesn't give a decent error message for an empty file.

2
glenn jackman On

Pretty compelling use of GNU datamash here:

read sum min max < <( datamash  sum 1 min 1 max 1 < "$1" )
[[ -z $sum ]] && echo "file is empty"
echo "sum=$sum; min=$min; max=$max"

Or, sort and awk:

sort -n "$1" | awk '
    NR == 1 { min = $1 }
    { sum += $1 }
    END {
        if (NR == 0) {
            print "file is empty"
        } else {
            print "min=" min
            print "max=" $1
            print "sum=" sum
        }
    }
'
0
Kamil Cuk On

a script that reads the file containing numbers (one per line) and writes their maximum, minimum and sum

Bash solution using sort:

<file sort -n | {
    read -r sum
    echo "Min is $sum"
    while read -r num; do
       sum=$((sum+num));
    done
    echo "Max is $num"
    echo "Sum is $sum"
}

Let's speed up by using some smart parsing using tee, tr and calculating with bc and if we don't mind using stderr for output. But we could do a little fifo and synchronize tee output. Anyway:

{ 
    <file sort -n | 
    tee >(echo "Min is $(head -n1)" >&2) >(echo "Max is $(tail -n1)" >&2) |
    tr '\n' '+'; 
    echo 0; 
} | bc | sed 's/^/Sum is /'

And there is always datamash. The following willl output 3 numbers, being sum, min and max:

<file datamash sum 1 min 1 max 1
2
Benjamin W. On

Here's how I'd fix your original attempt, preserving as much of the intent as possible:

#!/usr/bin/env bash

lines=$(wc -l "$1")

if [ "$lines" -eq 0 ]; then
    echo "File $1 is empty!"
    exit
fi

min=$(head -n 1 "$1")
max=$min
sum=0

while [ "$lines" -gt 0 ]; do
    num=$(tail -n "$lines" "$1")
    if [ "$num" -gt "$max" ]; then
        max=$num
    elif [ "$num" -lt "$min" ]; then
        min=$num
    fi
    sum=$(( sum + num ))
    lines=$(( lines - 1 ))
done

echo "Max: $max"
echo "Min: number $min"
echo "Sum: $sum"

The dealbreakers were missing linebreaks (can't use exit fi on a single line without ;); other changes are good practice (quoting expansions, useless use of cat), but wouldn't have prevented your script from working; and others are cosmetic (indentation, no backticks).

The overall approach is a massive antipattern, though: you read the whole file for each line being processed.


Here's how I would do it instead:

#!/usr/bin/env bash

for fname in "[email protected]"; do
    [[ -s $fname ]] || { echo "file $fname is empty" >&2; continue; }

    IFS= read -r min < "$fname"
    max=$min
    sum=0

    while IFS= read -r num; do
        (( sum += num ))
        (( max = num > max ? num : max ))
        (( min = num < min ? num : min ))
    done < "$fname"

    printf '%s\n' "$fname:" "  min: $min" "  max: $max" "  sum: $sum"
done

This uses the proper way to loop over an input file and utilizes the ternary operator in the arithmetic context.

The outermost for loop loops over all arguments.

0
ctac_ On

You can try with a shell loop and dc

while [ $# -gt 0 ] ; do
  dc -f - -e '
    ['"$1"' is empty]sa
    [la p q ]sZ
    z 0 =Z
# if file is empty
    dd sb sc
# populate max and min with the first value
    [d sb]sY
    [d lb <Y ]sM
# if max keep it
    [d sc]sX
    [d lc >X ]sN
# if min keep it
    [lM x lN x ld + sd z 0 <B]sB
    lB x
# on each line look for max, min and keep the sum
    [max for '"$1"' = ] n lb p
    [min for '"$1"' = ] n lc p
    [sum for '"$1"' = ] n ld p
# print summary at end of each file
  ' <"$1"
  shift
done