Showing a Bash Spinner for Long Running Tasks

by Louis Marascio on February 27, 2011

This post is part of a series: Bash Tips and Tricks.

I’ve mentioned before that I like to show users of my scripts useful information. This can be a complex progress bar, a simple progress meter, or it can be an animation to let the user know the script hasn’t hung. These animations are typically called Throbbers are exist purely to tell the user to continue patiently waiting. Sometimes folks like me call Throbbers Spinners for two reasons. First, a very common throbber animation type is the spinning wheel. Second, the word throbber sounds oddly sexual, and sort of creeps me out if I say it too many times in a sentence (Just kidding. Well, no, not really.)

This tip will show you to create a spinner for your Bash scripts. If you have a long running process and don’t want to try to tell the user approximately how much of that process is left to run, showing them a spinner is a great alternative.

Implementing the Spinner

The throbber, er I mean spinner, is implemented as a loop that shifts a string during each iteration.

spinner()
{
    local pid=$1
    local delay=0.75
    local spinstr='|/-\'
    while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
        local temp=${spinstr#?}
        printf " [%c]  " "$spinstr"
        local spinstr=$temp${spinstr%"$temp"}
        sleep $delay
        printf "\b\b\b\b\b\b"
    done
    printf "    \b\b\b\b"
}

Let’s dissect the spinner function so to illustrate how it works.

local pid=$1
local delay=0.75
local spinstr='|/-\'

This is pretty self-explanatory. The function has one input argument and two internal variables that control how it works.

  1. The input argument is assigned to the local variable pid. This is the process ID of the background task for which we are showing the spinner.

  2. The next local variable, delay, is how long each frame of the spinner animation stays visible for. We’ve set it to 75% of 1 second or 750ms. A smaller number will make the spinner rotate faster while a larger number will make it slower.

  3. The final local variable is called spinstr. This is a string for which each character is a frame in our spinner animation. As you can see, the string is 4 characters long, therefore we have four frames in our animation.

Moving along we get to the meat of the function, the primary loop.

while [ "$(ps a | awk '{print $1}' | grep -w $pid)" ]; do

The loop condition does three things:

  1. ps a will show all processes.

  2. awk '{print $1}' will extract the pid column of the process list.

  3. grep -w $pid will look for the process ID of our background task in the list of PIDs printed by awk.

The loop is conditioned on the return value of grep, the last command in our pipe chain. If grep finds a match in our PID list it will return 0, otherwise it will return 1.

The next several lines do the hard work of displaying our animation. I’ve created two images to help illustrate what’s going on.

First, I remove the first character from the string and save the remaining characters into a temp.

local temp=${spinstr#?}

Then I use printf to output the first character of spinstr, which contains our animation. Only the first character is output because I use the %c format string.

printf " [%c]  " "$spinstr"

These two steps are illustrated below.

Bash Spinner Steps 1 and 2

Finally, I shift spinstr by constructing a new string that contains the value of temp and all characters from spinstr that aren’t in temp.

local spinstr=$temp${spinstr%"$temp"}

The first part of this, the character rotation, appears as step 3 and the last part, the assignment to spinstr appears as step 4.

Bash Spinner Steps 3 and 4

Using the Bash Spinner

When you have a task to run that will take a large (or unknown) amount of time invoke it in a background subshell like this:

(a_long_running_task) &

Then, immediately following that invocation, call the spinner and pass it the PID of the subshell you invoked.

spinner $!

The $! is a bash internal variable for the PID of the last job run in the background. In this case, it will give us the PID of the bash shell executing our long running task.

When it’s all said and done you’ll have a nice and simple Bash spinner like the one below.

A Bash Spinner

Previous post:

Next post: