Quick Links
Sometimes it’s useful to make sure a Bash shell script doesn’t run too frequently. Here’s a neat way to set a time limit that must expire before the script can run again.
Take a Breather
Depending on what it’s doing and which other processes it may launch, a Bash script can consume as much RAM and CPU time as any other resource-hungry process. It can be useful to limit how frequently such a script can be launched.
Enforcing a period of respite between each run of a heavyweight script stops it from hogging resources. Computationally expensive scripts can monopolize computers to the point where other users experience a drop in performance. In extreme cases, if the script causes a lot of disk churn, for example, it can even hasten the demise of your hardware.
Of course, engineering a scheme that limits how soon you can relaunch a script adds code to your script, and gives it one more thing to do. That might sound counterproductive, but if the checks are lightweight and fast, their tiny overheads are far outweighed by their resource savings.
We want to enforce a minimum time period that must expire before a script can be repeated. We’ll call this period the interim. We’ll define it as the time between the previous run concluding and the new run starting.
We’ll need to save the time at which the script completes, so that the next time the script is launched we can retrieve it. Because the script can easily determine the current time, it can work out the difference between its launch time and the previous script’s termination time.
If that difference is less than our acceptable interim, the script will exit.
Linux has been counting the seconds since the (second) Linux epoch, which happened at midnight on Jan. 1, 1970, UTC. We can see the time and date by issuing the date command.
dateWe can pass format specifiers to date to get the output in different renderings. To see the time in seconds since the epoch, use a lowercase ‘s’:
date +%sBeing able to access the time as a single integer makes it very easy to compare two times, and to figure out the time span between them. That’s perfect for our needs, and we'll make good use of that.
We can easily write the time into a file, just by redirecting the output of the date command. We can use cat to verify that it worked.
date +%s > temp.datcat temp.dat
That gives us a way to store our timestamp. Note that we're using a single > for the redirection, so the file is recreated each time, and only ever holds a single entry.
Inside our script, we’ll need to open the timestamp file and read the saved value. That value needs to be held in a variable so that our script can use it.
That sounds fairly involved, but there’s a simple trick we can use. Inside our script, we’ll use the source command to read the timestamp file. The commands inside the sourced file are executed as though they were commands inside our script.
When we save the timestamp, we’ll actually save a command that creates a variable and assigns the time value to it. When the file is sourced, our script will execute that command, create the variable, and store the time value in the variable, so it's all done for us.
We can check that process on the command line. We compose and write a command to a file. The command creates a variable called previous_exit that's set to the number of seconds since the epoch. We source the file. We then check that a variable called previous_exit now exists, and see what value it holds.
echo "previous_exit=$(date +%s)" > timestamp.logsource timestamp.log
echo $previous_exit
If we examine the contents of the file, we can verify that the value held by the variable is the one that's in the file.
cat timestamp.logThat’s a nice and easy solution to storing and retrieving our time value.
Putting It All Together
Let’s go through the different elements of the script.
My script will store the timestamps in a file called .timestamp.log. Note the first character is a period ‘.’ which means it’s a hidden file. It’s going to be stored in my home directory.
The script creates a variable called timestamp_log to hold the path and filename.
Next, a function called set_timestamp is defined. When it is called, this function writes the value passed to it, to the timestamp.log file.
#!/bin/bash# location of the timestamp log file
timestamp_log="/home/dave/.timestamp.log"
set_timestamp() {
echo "previous_exit=$1" > $timestamp_log
}
Because the timestamp.log file is updated (and created if it doesn’t exist) when the script exits, the very first time the script runs, the timestamp.log file will not exist. That would cause a problem when the script tries to read from it.
To overcome that issue, and to protect against situations where the timestamp.log file might have been deleted, we test for the existence of the file. If it doesn’t exist, we create it by storing a dummy time value of zero to it.
# If the timestamp log file doesn't exist, create itif [ ! -f $timestamp_log ]; then
set_timestamp 0
fi
Now we can safely source the file and read in the instruction inside it. This sets the variable previous_exit to the previous timestamp.
# get the last exit time as variable called previous_exitsource $timestamp_log
Now that we’ve got the timestamp value, we can calculate the interim period between the previous timestamp and the time now.
# get the interim period since the last exitinterim=$(( $(date +%s)-$previous_exit ))
Now we can perform a simple test to see whether enough time has elapsed for the script to be permitted to run. I’m using an arbitrary and short value of five seconds for testing.
if (( $interim <= 5 )); thenecho "Too soon... $interim seconds..."
exit 1;
fi
# your actual script starts here
echo "Running..."
If the interim period is over five seconds, the script can proceed. When it completes, we write the current time to the timestamp.log file by calling our set_timestamp function.
# set the new timestampset_timestamp $(date +%s)
exit 0
Here’s the entire script.
#!/bin/bash# location of the timestamp log file
timestamp_log="/home/dave/.timestamp.log"
set_timestamp() {
echo "previous_exit=$1" > $timestamp_log
}
# If the timestamp log doesn't exist, create it
if [ ! -f $timestamp_log ]; then
set_timestamp 0
fi
# get the last exit time as a variable called previous_exit
source $timestamp_log
# get the interim period since the last exit
interim=$(( $(date +%s)-$previous_exit ))
if (( $interim <= 5 )); then
echo "Too soon... $interim seconds..."
exit 1;
fi
# set the new timestamp
set_timestamp $(date +%s)
echo "Running..."
exit 0
Copy this into your favorite editor, and save it as a file called tc.sh. Remember to change the timestamp_log= value on line 4 to point to the location on your computer where the timestamp.log should be stored.
Make your script executable.
chmod +x tc.shAnd now we can run it.
./tc.shSubsequent execution attempts within the five-second exclusion period self-terminate. After five seconds have elapsed, we can run the script again.
Useful In Other Scenarios
Remember, if your script has branching execution and could exit at different points within the script, you’ll need to call set_timestamp before each possible exit. That’s why it was worth creating the set_timestamp function, even though it’s only used twice in this script.
The trick of storing the variable name and its value in a file which is sourced, can be leveraged to read in a config file. You'll just need to write a list of variable names and their values to a file, and source it from your script.