Creating a Bash Spinner

June 9th, 2009 by Mitch Frazier in

Your rating: None Average: 4.5 (15 votes)

Do you know what this sequence of characters "/-\|/-\|" is for? A text based spinner. Still confused, read on.

You've probably seen some long running console program that shows a spinner while it runs. A spinner being the aforementioned sequence of characters output one after the other at the same place on the screen with a pause between each character. The code in this article creates a spinner which runs in a separate process. By doing this the spinner spins at a constant rate and doesn't pause when your script pauses. It also eliminates the need to sprinkle spinner output messages throughout your program.

In addition the spinner reads from a log file created by the main process and displays the last line from the log file next to the spinner. When the log file goes away the spinner exits.

This is the script for the main process, called runner.sh:

#!/bin/bash

logfile=/tmp/mylog

echo >$logfile
trap "rm -f $logfile" EXIT

# Output message to log file.
function log_msg()
{
    echo "$*" >>$logfile
}


# Start spinner
sh spinner.sh &

# Perform really long task.
i=0
log_msg "Starting a really long job"
while [[ $i -lt 100 ]]
do
    sleep 1
    let i+=5
    log_msg "$i% complete"
done

sleep 1
echo

The function at the top is to output a message to the log file. The log file is initialized to an empty file at the top of the program and the output function appends to it.

Before entering its main loop, the main process starts the spinner in the background. After that it just pauses a bit, outputs a status message and then repeats till it's 100% complete.

The spinner process follows:

#!/bin/bash

logfile=/tmp/mylog
logsize=0
spinpause=0.10
linelen=0


# Output last line from log file.
function lastout()
{
    local line=$(tail -n 1 $logfile 2>/dev/null)
    if [[ "$line" ]]; then
        echo -n "     $line"

        # Erase any extra from last line.
        local len
        let len=$linelen-${#line}
        while [[ $len -gt 0 ]]
        do
            echo -n " "
            let len--
        done
        linelen=${#line}
    fi
}

# Output a spin character.
function spinout()
{
    local spinchar="$1"
    local sz
    local ll
    if [[ -f $logfile ]]; then
        echo -n -e "\r$spinchar"
        sleep $spinpause

        # Check for new message.
        sz=$(stat --printf '%s' $logfile 2>/dev/null)
        if [[ $sz -gt $logsize ]]; then
            lastout
            logsize=$sz
        fi
    fi
}

if [[ -f $logfile ]]; then
    logsize=$(stat --printf '%s' $logfile 2>/dev/null)
    if [[ $logsize -gt 0 ]]; then
        echo -n " "
        lastout
    fi

    while [[ -f $logfile ]]
    do
        spinout "/"
        spinout "-"
        spinout "\\"
        spinout "|"
        spinout "/"
        spinout "-"
        spinout "\\"
        spinout "|"
    done
    echo
fi

The spinner contains two functions. The first function outputs the last line from the log file. Since the log file line is always placed on the same screen line it's possible that the new log line is shorter than the last log line. So, after outputting the new line the function outputs spaces up to the length of the previous log line so that it is completely erased.

The second function outputs a spinner character and pauses for the inter-character pause time. It precedes the spinner character with a carriage return so that everything remains on the same line. After outputting the spinner character it checks to see if the log file has grown in size since the last time it output a line. If it has, it then outputs a new line.

The main loop of the spinner outputs each of the spinner characters, one after the other.

Watch the attached video the see it run.

__________________________

Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.

AttachmentSize
spinner.mp41.05 MB
spinner.ogv1.55 MB


Special Magazine Offer -- Free Gift with Subscription
Receive a free digital copy of Linux Journal's System Administration Special Edition as well as instant online access to current and past issues. CLICK HERE for offer

Linux Journal: delivering readers the advice and inspiration they need to get the most out of their Linux systems since 1994.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Anonymous's picture

redundancy

On June 10th, 2009 Anonymous (not verified) says:

is there a reason why you print two identical sets of the characters instead of just one (

/-\|

)?

Mitch Frazier's picture

Just Goofy I Guess

On June 10th, 2009 Mitch Frazier says:

Nope, it's not necessary. I guess I just starting rotating the spinner image in my brain to count the positions and never noticed when the characters actually repeated.

__________________________

Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.

Anonymous's picture

Here's my 'spin' on this tip :)

On June 10th, 2009 Anonymous (not verified) says:

Here's one I wrote a while ago. It basically puts the twirly characters in a string and cuts the "next" character in the animation sequence. The term "twirly" is coined by my then-3yr-old ;)

I can't get the "code" HTML tag to properly format this code block. If someone at LJ can make it pretty like Mitch's, that would be most appreciated ;).

#!/bin/bash
shopt -s xpg_echo

twirl() {
    tc="`echo $twirlstr | cut -c$j`"
    col=$1
    [[ "${tc}" == "\\" ]] && echo "${esc}[${col}G\\${tc}\c"
    [[ "${tc}" == "\\" ]] || echo "${esc}[${col}G${tc}\c"

    [[ $j -eq 5 ]] || ((j++))
    [[ j -eq 5 ]] && j=1
}

twirlstr="-\|/"
declare -i j=1
esc="\\033"

echo "This is a twirly example:"
for (( i=1 ; i < 100 ; i++ ))
do
    echo "${esc}[0GDoing stuff...............................\c" #"

    # Print the next twirly character at colume 45
    twirl 45

    # lines of code to "do stuff"

done
echo "\n"

Of course, this is just a sample...the "for" loop would realistically be something more like a "while" or some such thing. This scriplet uses ANSI VTxxx escape sequences to place the cursor.

Mitch Frazier's picture

Preeeeeeeeeeee

On June 10th, 2009 Mitch Frazier says:

That would be <pre>...</pre>, I guess we have droopy set to filter out <code> tags.

Fixed.

Thanks for the twirly!

__________________________

Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.

Mitch Frazier's picture

Let me re-phrase that

On June 10th, 2009 Mitch Frazier says:

Apparently <code> tags aren't filtered out, they just don't seem to work correctly. Oh webmaster...

__________________________

Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.

Jeff Rasmussen's picture

Another Correction

On June 9th, 2009 Jeff Rasmussen (not verified) says:

I wasn't able to get the 2 scripts to work until I changed the call in runner.sh

From:
sh spinner.sh &

To:
bash spinner.sh &

Mitch Frazier's picture

Default shell

On June 9th, 2009 Mitch Frazier says:

Your default shell must not be bash. Probably better to make spinner.sh executable and execute it via

./spinner.sh &

Just make sure that the copy of spinner.sh that you have doesn't have the typo referred to in the comment below.

__________________________

Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.

samba's picture

Minor typo

On June 9th, 2009 samba (not verified) says:

FYI, just a small typo...

The second code section starts with:

#!/bin/basn

Great article though! I look forward to using this in my code!

Mitch Frazier's picture

Fixed

On June 9th, 2009 Mitch Frazier says:

Thanks.

__________________________

Mitch Frazier is an Associate Editor for Linux Journal and the Web Editor for linuxjournal.com.

Post new comment

Please note that comments may not appear immediately, so there is no need to repost your comment.
The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <pre> <ul> <ol> <li> <dl> <dt> <dd> <i> <b>
  • Lines and paragraphs break automatically.

More information about formatting options

Newsletter

Each week Linux Journal editors will tell you what's hot in the world of Linux. You will receive late breaking news, technical tips and tricks, and links to in-depth stories featured on www.linuxjournal.com.
Sign up for our Email Newsletter

Tech Tip Videos

From the Magazine

December 2009, #188

If last month's Infrastrucuture issue was too "big" for you then try on this month's Embedded issue. Find out how to use Player for programming mobile robots, build a humidity controller for your root cellar, find out how to reduce the boot time of your embedded system, and if you're new to embedded systems find out the basics that go into one. You can also read about the Beagle Board, the Mesh Potato and a spate of other interestingly named items. And along with our regular columns don't miss our new monthly column: Economy Size Geek.







Read this issue