Martin
Martin

Reputation: 1177

last line handling in bash while loop

I have a file which contains following 10 numbers:

> cat numbers
9
11
32
88
89
90
95
104
118
120
>

I would like to print out the preceding number only if it is at least 5 numbers smaller than the current number. So I expect output like this:

11
32
90
95
104
120

I have a script which does this:

> cat test.sh
#!/bin/bash

subtraction="5"

while read -r number; do

  if [ -n "$previous_number" ] && (( $((number - subtraction)) >= previous_number )); then
    echo "$previous_number"
  fi

  previous_number="$number"

done < "$1"
> ./test.sh numbers 
11
32
90
95
104
> 

However, it doesn't print 120. What is the most elegant/proper solution in such cases? Should I simply add tail -1 "$1" after the while loop?

Upvotes: 0

Views: 232

Answers (2)

Charles Duffy
Charles Duffy

Reputation: 295403

For someone else reading this for whom while read genuinely is not iterating over the last line of a file, there's a likely different problem: An input file without a trailing newline.

For that, one can amend their code as follows:

while read -r number || [[ $number ]]; do
  : "...logic here..."
done

This is true because without a trailing newline, read will return false, and so the body of the loop will not be executed with the original code, but $number is still populated.


However, for this specific program and its specific input given, there's nothing at all wrong with how the while read idiom handles the last line of an input; the output at hand follows from the program's logic as written and defined.

Consider the following version, which makes what's happening more clear:

#!/bin/bash
subtraction="5"
while read -r number; do
  if [[ $previous_number ]] && (( (number - subtraction) >= previous_number )); then
    printf '%q is at least %q away from %q\n' "$previous_number" "$subtraction" "$number"
  else
    printf '%q is not %q away from %q\n' "$previous_number" "$subtraction" "$number"
  fi
  previous_number="$number"
done <"$1"

Its output is:

'' is not 5 away from 9
9 is not 5 away from 11
11 is at least 5 away from 32
32 is at least 5 away from 88
88 is not 5 away from 89
89 is not 5 away from 90
90 is at least 5 away from 95
95 is at least 5 away from 104
104 is at least 5 away from 118
118 is not 5 away from 120

...as this last line of output shows, it is genuinely considering 120, and deciding not to print it per your program's logic as defined.

Upvotes: 2

anubhava
anubhava

Reputation: 785128

It is easier to use awk for this job:

awk 'NR>1 && $1-p>=5{print p} {p=$1}' file

Output:

11
32
90
95
104

btw 120 won't be printed in output because preceding number is 118 which is not <=5 to 120.

Upvotes: 2

Related Questions