15

I am new to bash scripting and started out with a few sample scripts.

One is:

#!/bin/bash
SECONDS=5
i=1

while true
do
        echo "`date`: Loop $i"
        i=$(( $i+1 ))
        sleep $SECONDS
done

This results in:

Sunday 10 May  15:08:20 AEST 2020: Loop 1
Sunday 10 May  15:08:25 AEST 2020: Loop 2
Sunday 10 May  15:08:35 AEST 2020: Loop 3
Sunday 10 May  15:08:55 AEST 2020: Loop 4

... and is not what I expected or wanted the script to do.

Why would the seconds double, every time it runs through the loop

bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
MaxG
  • 403

2 Answers2

28

Because SECONDS is an internal variable in bash. It stores the amount of time that the shell has been running.

From man bash:

SECONDS
Each time this parameter is referenced, the number of seconds since shell invocation is returned. If a value is assigned to SECONDS, the value returned upon subsequent references is the number of seconds since the assignment plus the value assigned.

So, your script works like this:

  1. On first use it accepts the value of 5 as input, the initial value is 5.
  2. Then there is an sleep of 5 seconds, when that sleep returns, the value of SECONDS will be the 5 seconds of the initial value + 5 seconds of the sleep = 10 seconds
  3. Then there is an sleep of 10 seconds (the present value of SECONDS) plus the previous accumulated time of 10 seconds will give 20.
  4. Repeat an sleep for 20 seconds plus the previous value of 20 is 40.
  5. etc.
  • 2
    (damn)... lots to learn... why I opted to use a character like Z_ before any variable, to ensure I am not using system defined variables. – MaxG May 10 '20 at 06:08
  • 15
    Another rule could be to use lowercase variables (seconds) instead of the Internally defined variable (SECONDS). There are very few variables in lowercase that are special to bash. In fact, the rule is that environment variables should be in uppercase. So, from both cases, do not use UPPERcase variables, please!. @MaxG –  May 10 '20 at 06:12
6

There is a second-order problem with this construct: if the processing in the loop takes significant time (say 1.4 seconds), then the loop will only repeat every 6.4 seconds.

If that is important, you can count loops instead.

#!/bin/bash

Expired=$(( SECONDS + 23 ))
Tick=5
Iter=1

while (( SECONDS < Expired )); do
    echo "$(date '+%T.%N'): Loop $Iter"
    sleep 1.4  #.. Simulate work.
    echo "$(date '+%T.%N'): Idle"
    sleep $(( Iter++ * Tick - SECONDS ))
done                    

The loop times are stable within the integer accuracy of SECONDS.

You can do more complex arithmetic to initialise Expired (e.g. use two date commands to find seconds before midnight).

Paul_Pedant
  • 8,679