RSS Feed Share on Twitter

All Shell Scripting Tips

28 Mar 2018

Spinner

Creating a simple Spinner to show a script is still running

In this tips section we already have a Progress Bar article, which will show updates to the user whilst some long-running process is happening in the background.

This is similar, but in some ways the opposite: This spinner runs in the background, whilst we wait for a long-running process in the foreground to complete. Also, because it does not know how long it will be running for, instead of processing from the left to the right of the screen, it simply spins indefinitely, a bit like those irritating mouse cursors when Windows is having one of its 'moments'.

The specific reason I wrote this today, was that a certain environment had a firewall which would time-out inactive sessions. My script was using cUrl to call into a web service which would take many minutes to complete, before sending output back to the script. In the meantime, the firewall would have decided that nothing was happening in the session, and kill the SSH session, logging the user out in the process. So having this regularly updating the screen is one way to ensure that the connection stays alive. (see "Keepalive" notes below)

For the purposes of demonstration, this script uses http://slowwly.robertomurray.co.uk/, a useful service which is deliberately slow to respond. When it does, we get it to pass us the contents of https://www.shellscript.sh/tips/spinner/test.txt, a simple test file containing a couple of lines of text:

Hello.
This is the test file.

The command as it runs:


Download spinner.sh
#!/bin/bash

spin()
{
  spinner="/|\\-/|\\-"
  while :
  do
    for i in `seq 0 7`
    do
      echo -n "${spinner:$i:1}"
      echo -en "\010"
      sleep 1
    done
  done
}

echo "About to make a slow web call..."

# Start the Spinner:
spin &
# Make a note of its Process ID (PID):
SPIN_PID=$!
# Kill the spinner on any signal, including our own exit.
trap "kill -9 $SPIN_PID" `seq 0 15`

# http://slowwly.robertomurray.co.uk/ is a useful 
# webservice - it is deliberately slow!
curl -L http://slowwly.robertomurray.co.uk/delay/15000/url/https://www.shellscript.sh/tips/spinner/test.txt

echo "Finished."

# If the script is going to exit here, there is nothing to do.
# The trap above will kill the spinner when this script exits.
# Otherwise, if the script is going to do more stuff, you can
# kill the spinner now:
kill -9 $SPIN_PID

The spin() function

The spin function defines the $spinner variable as a string containing each of the characters of the spinner: / | \ - / | \ -. Each of these is displayed in turn. Notice that the backslashes (\) have to be escaped; otherwise, \- would be interpreted as a backslashed - symbol, and the backslash itself would be ignored.

The loop then displays ${spinner:$i:1} - that is, the ith character of the $spinner variable. The -n ensures that the echo is not followed by a newline. Then the echo -en "\010" emits Octal character 010, which is the backspace code in ASCII. This puts the cursor back over the previous character, ready to overwrite it.

After a sleep 1, it goes through the loop again.

Because the whole thing is wrapped in a while : loop, it will continue displaying the spinner forever (until it is killed).

The calling process

The calling process runs spin in the background; this will execute in a separate shell. The caller makes a note of its Process ID (PID) via the special variable $!. $! always contains the PID of the most recently-run background process.

The trap tells the shell to run kill -9 $SPIN_PID on any signal 0-15. This ensures that as soon as our script exits (Signal 0), is interrupted (Signal 1), etc, the spinner is killed. If the spinner kept on running after the main script had ended for whatever reason, then the display would look messy.

Now the spinner has been set up, the main script calls the curl command, which will potentially take a long time. The spinner will be updated every second, while the curl displays nothing as it waits for a response. The spinner is now keeping the terminal's SSH session alive.

Finally, the script can choose to kill -9 $SPIN_PID or - if it is about to terminate anyway, it can allow the trap to run that automatically as the parent script exits.

A nice side-effect of the way in which the spin() function displays a character and then immediately backspaces over it, is that when it is killed, it is almost guaranteed that the spinner character itself will not be shown. There is no sign, once the script has finished, that the spinner was ever there at all.

Footnote: Keepalive

It is possible for the SSH protocol to keep the session alive, even if nothing appears to be happening. With traditional SSH, you could put this in your $HOME/.ssh/config, to send a "keepalive" message every 60 seconds, and only tear down the connection if 3 consecutive keepalives are dropped:

Host *
  ServerAliveInterval 60
  ServerAliveCountMax 3

On the server side, you can also enforce this; add these entries to /etc/ssh/sshd_config and restart sshd:

ClientAliveInterval 60
ClientAliveCountMax 3

With the PuTTY SSH client, you can put a value (say, 60) by "Seconds between keepalives (0 to turn off)" in the "Connection" tab.

 

 

 


You can buy the content of this tutorial as a PDF to download to all of your devices!

Contact

You can mail me with this form. If you expect a reply, please ensure that the address you specify is valid. Don't forget to include the simple addition question at the end of the form, to prove that you are a real person!