Fadi Fadi - 1 year ago 72
Bash Question

Spinner in Shell

I found this for BASH, but I want to do the same thing with shell (


The twist is to make it a timer also, so like wait 60 seconds for example until it ends.

Answer Source

Fadi's own solution is helpful (and comes by courtesy of Adam Katz's comment on the linked answer), but comes with 2 caveats:

  • The spinner, due to using \r, only works at the beginning of a line.
  • Per POSIX, sleep only supports integral seconds.

It may also not be readily obvious where to test for whether the operation is done and how to exit the two loops vs. how to use a spinner as a background job, while waiting for a blocking command to finish.

The following snippets address these issues; they use \b (backspace) rather than \r, which allows the spinner to be displayed with preceding text, if desired:

Asynchronous case (poll for completion):

If you're waiting for completion of a process asynchronously (by checking for completion periodically, in a loop):

printf 'Processing: '
while :; do
  for c in / - \\ \|; do # Loop over the sequence of spinner chars.
    # Print next spinner char.
    printf '%s\b' "$c"
    # Perform your custom test to see if the operation is done here.
    # In this example we wait for a file named 'results' to appear.
    # Note that `[ -f results ] && ...` is just a shorter alternative to
    # `if [ -f results]; then ... fi`.
    [ -f results ] && { printf '\n'; break 2; } # Print a newline, break out of both loops.
    sleep 1 # Sleep, then continue the loop.

The above, due printing the \b char. after the spinner char., displays the cursor behind the spinner char; if that is aesthetically undesirable, use the following variation:

printf 'Processing:  ' # note the extra space, which will be erased in the first iteration
while :; do
  for c in / - \\ \|; do
    printf '\b%s' "$c" 
    [ -f results ] && { printf '\n'; break 2; }
    sleep 1

A more complete example with configurable timeout and sleep interval (note that the timeout enforcement will not be exact, as the time it takes to process each loop iteration is not taken into account; in bash, you could simply reset special var. SECONDS before the loop starts and then check its value):

# Determine how long to sleep in each iteration
# and when to timeout (integral seconds).
sleepInterval=1 timeout=10 elapsed=0 timedOut=0

printf 'Processing:  ' # note the extra space, which will be erased in the first iteration
while :; do
  for c in / - \\ \|; do
    printf '\b%s' "$c" 
    [ -f results ] && { printf '\nDone.\n'; break 2; }
    [ $elapsed -ge $timeout ] && { timedOut=1; printf '\nTIMED OUT\n' >&2; break 2; }
    sleep $sleepInterval
    elapsed=$(( elapsed + sleepInterval ))

Synchronous (blocking) case:

If you're waiting for a lengthy synchronous (blocking) command to complete, the spinner must be launched as a background job, which you then terminate once the blocking call has completed.

printf 'Processing: '
# Start the spinner in the background.
# The background job's PID is stored in special variable `$!`.
(while :; do for c in / - \\ \|; do printf '%s\b' "$c"; sleep 1; done; done) &

# Run the synchronous (blocking) command.
# In this example we simply sleep for a few seconds.
sleep 3

# The blocking command has finished:
# Print a newline and kill the spinner job.
{ printf '\n'; kill $! && wait $!; } 2>/dev/null

echo Done.