Today, I’ll show you how to make a command line countdown timer for Bash, using Bash functions. We’ll discuss the approach behind the problem before writing any code, so that way our solution will be robust and satisfy our needs.
I made this tool when I noticed myself opening DuckDuckGo or Google and searching for “countdown timer” when I needed one. By writing a few lines of Bash, I can start a countdown timer without having to open a web browser.
Intuition
Before we start writing code, let’s think about the intuition behind making a
countdown timer. We’ll want to keep in mind what common functions we already
have access to in the terminal, such as date
, and make use of those.
So, what is a countdown timer? Suppose you set a countdown for thirty seconds. You expect that thirty seconds after you start it, a sound will go off. But we need to translate that into something that we could write in Bash.
This is where we need to find a balance between complexity and utility. In theory, you might be able to set a countdown timer for a certain number of seconds, or minutes, or hours, or some combination of those, like one hour and five minutes. The ability to enter any arbitrary time, like 1 hour, 23 minutes, and 5 seconds, could make the input handling relatively complex. For my use case, I only needed to set minutes, and sometimes hours, but never a combination of the two. Based on these requirements, I could simply the input quite a bit.
When writing software (at least in my experience) it’s common to use seconds as a base unit of time measurement, translating that to minutes or hours by some multiplication when needed. So we can make a timer based on seconds, and then extend it to support minutes and hours relatively easily.
The other thing we should think about is how to keep track of the passing of
time. A good first approach is to make use of sleep n
, which pauses execution
for n seconds. If you don’t need to display the remaining time, then this might
be the a simple, reliable solution. But often we want to display how much time
is remaining, and if you start to think about using sleep 1
and keeping track
of the number of seconds that have passed, then you’ll face some trouble when
trying to print the remaining time on the screen. If someone sets a timer for 80
seconds, it would make sense to display the time remaining in a proper format,
starting at 01:40 and then going to 01:39, 01:38, and so on.
All of this conversion will end up making your program more of a time calculator than a simple countdown timer!
As I mentioned at the beginning, date
, a utility which shows the current
system time, will be useful for solving this problem because it will take care
of the time formatting.
Now that we have thought about the major considerations for this problem, let’s begin writing the function that will do the majority of the work.
The main function to handle seconds
I typically put these in a separate file called .bash_functions
which I then
include in my .bashrc
, like this:
if [ -f ~/.bash_functions ]; then
. ~/.bash_functions
fi
I give these functions short names because they’re faster to type when you need to launch a timer.
First, we’ll figure out when the timer should stop. If we set a timer for 30 seconds, we want the time to stop 30 seconds in the future. We can get this time by getting the current Unix time and adding the number of seconds that we set.
+%s
says “format the date output as the number of seconds since the Unix
epoch. The $1
is the first argument that we give when calling the function.
For example, if we were to run ts 10
in the terminal, $1
would be the value
10
. The $()
that wraps around the whole expression allows us to store the
output in a variable, which we have called date
.
function ts()
{
date=$((`date +%s` + $1));
}
Next, we’ll add a loop that checks if the current Unix time is less than the end time, and if so, it prints out the amount of time remaining every second.
The while loop condition uses the -ne
(not equal) comparison to perform our
check. There’s a little more going on here in terms of syntax (fun fact, [
is actually a command, not simply parenthesis like in other programming
languages), but for the sake of this post we won’t dive deep into the syntax
of Bash.
Inside the loop, we print out the current time remaining. Essentially, we get
the number of seconds remaining with subtraction and then use the date
command again, this time giving it a date (in terms of seconds) to print out
in the given format, +%H:%M:%S
. There’s actually quite a lot going on in this
one line, so let’s look at the function so far and then explore how that one
line works.
function ts()
{
date=$((`date +%s` + $1));
while [ "$date" -ne `date +%s` ]; do
echo -ne " $(date -u --date @$(($date - `date +%s`)) +%H:%M:%S)\r";
sleep 1
done
}
Let’s start from the inside and work our way out. In $date - `date +%s`
,
we subtract the current date in seconds since the Unix epoch from the future
date we stored in the date
variable. The result is the number of seconds until
the end date.
$(($date - `date +%s`))
allows us to use the resulting value in other places.
In this case, it becomes the argument to the --date
flag. The extra parenthesis
are needed so that the inner expression fully evaluates first. Try playing
around with $(4 + 3)
in your terminal. You can’t do this because you can’t do
arithmetic operations wherever you want in Bash. The shell will treat 4
as a
command to be executed.
Then we call date
with a couple flags, one of them being -u
for UTC time.
This prevents the +/- adjustments that the clock makes for your time zone.
Hopefully you will realize that we’re kind of abusing date
.
The last part of the date
command is +%H:%M:%S
, which prints out the date
in the given format of hours : minutes : seconds.
The -ne
flags for echo
are not the -ne
that means not equal to. These flags
just happen to have the same letters, but they are really two separate flags.
n
tells echo to not print newlines, and e
allows us to use backslashes. We
need to a backslash to print the \r
character, which return the “carriage” to
the start of the line. You might see where this is going.
After the echo
, we sleep
for one second and start the loop again.
The creative \r
allows us to print the remaining time in the same place on the
screen, giving the appearance of a traditional countdown timer.
Playing sound
After sleep 1
, I have the line play_sound ~/downloads/whatever.wav
, and my
play_sound
function is as follows.
function play_sound()
{
cat $1 | aplay
}
However, the method to play sound might differ depending on your operating system. This works for me on Debian.
Some helpers for minutes and hours
If you want to set a timer for minutes and hours without doing math in your head, some quick helper functions that call the original one will do the trick.
function tm()
{
echo "Timer set for $1 minutes"
ts $1*60
}
function th()
{
echo "Timer set for $1 hours"
ts $1*60*60
}
End result
Putting everything together, this is what we created:
function ts()
{
date=$((`date +%s` + $1));
while [ "$date" -ne `date +%s` ]; do
echo -ne " $(date -u --date @$(($date - `date +%s`)) +%H:%M:%S)\r";
sleep 1
done
play_sound ~/downloads/whatever.wav
}
This is an example of what the output looks like, with the time remaining updating every second.
john@thinkpad ~ $ tm 4
Timer set for 4 minutes
00:03:53
We did some clever things with date
to create our own command line timer.
Hopefully this gives you an idea of how to approach a programming problem
as well as insights on how to use many small Unix utilities to make something
useful.