John Jago

Making a command line countdown timer

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.