13

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>
1
26

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
  • 1
    (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 6:08
  • 14
    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
    – ImHere
    May 10 '20 at 6:12
5

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).

2
  • 3
    Another problem specific to bash is that SECONDS is incremented from 0 to 1 not after one second of bash starting but between 0 and 1 second after it was started. You may want to switch to ksh93 or zsh where that problem was fixed and which support floating point arithmetics and have builtin ways to sleep with subsecond granularity. See Running a loop precisely once per second or Repeat function of bash x minute(s) May 10 '20 at 11:43
  • I am using this method when programming Arduino UNO micro-controllers... allowing the rest of the program to keep working.
    – MaxG
    May 11 '20 at 9:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.