Display Spinner while waiting for some process to finish

How can I show spinner till command line finish it is job? In other words, If I am running a script and I want to show spinner while this script is running and the spinner disappears when the script finish it is job.

Bellow is a common spinner code:

i=1
sp="/-|"
echo -n ' '
while true
do
printf "b${sp:i++%${#sp}:1}"
done

How can I link the previous spinner code to a command to let it show spinner while the command is running and the spinner disappears when the command finish it is job? If I include the command inside the loop it will loop with the spinner so what is the solution in this case?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Have your while loop watch for your real command to exit. I’ll assume a Linux environment that has /proc entries for each PID, but you could slice it other ways:

#!/bin/bash
# your real command here, instead of sleep
sleep 7 &
PID=$!
i=1
sp="/-|"
echo -n ' '
while [ -d /proc/$PID ]
do
  printf "b${sp:i++%${#sp}:1}"
done

Method 2

Here’s another fancy spinner which you can use like this:

spinner ping google.com
echo "ping exited with exit code $?"

spinner sleep 10
echo "sleep exited with exit code $?"

It has 12 themes and picks one randomly.

#!/bin/bash
# Shows a spinner while another command is running. Randomly picks one of 12 spinner styles.
# @args command to run (with any parameters) while showing a spinner. 
#       E.g. ‹spinner sleep 10›

function shutdown() {
  tput cnorm # reset cursor
}
trap shutdown EXIT

function cursorBack() {
  echo -en "33[$1D"
}

function spinner() {
  # make sure we use non-unicode character type locale 
  # (that way it works for any locale as long as the font supports the characters)
  local LC_CTYPE=C

  local pid=$1 # Process Id of the previous running command

  case $(($RANDOM % 12)) in
  0)
    local spin='⠁⠂⠄⡀⢀⠠⠐⠈'
    local charwidth=3
    ;;
  1)
    local spin='-|/'
    local charwidth=1
    ;;
  2)
    local spin="▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"
    local charwidth=3
    ;;
  3)
    local spin="▉▊▋▌▍▎▏▎▍▌▋▊▉"
    local charwidth=3
    ;;
  4)
    local spin='←↖↑↗→↘↓↙'
    local charwidth=3
    ;;
  5)
    local spin='▖▘▝▗'
    local charwidth=3
    ;;
  6)
    local spin='┤┘┴└├┌┬┐'
    local charwidth=3
    ;;
  7)
    local spin='◢◣◤◥'
    local charwidth=3
    ;;
  8)
    local spin='◰◳◲◱'
    local charwidth=3
    ;;
  9)
    local spin='◴◷◶◵'
    local charwidth=3
    ;;
  10)
    local spin='◐◓◑◒'
    local charwidth=3
    ;;
  11)
    local spin='⣾⣽⣻⢿⡿⣟⣯⣷'
    local charwidth=3
    ;;
  esac

  local i=0
  tput civis # cursor invisible
  while kill -0 $pid 2>/dev/null; do
    local i=$(((i + $charwidth) % ${#spin}))
    printf "%s" "${spin:$i:$charwidth}"

    cursorBack 1
    sleep .1
  done
  tput cnorm
  wait $pid # capture exit code
  return $?
}

("<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="163256">[email protected]</a>") &

spinner $!

Method 3

This shell script should do what you’re looking for:

#!/usr/bin/env bash

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

("<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e0c4a0">[email protected]</a>") &
show_spinner "$!"

Assuming you store the shell script in a file named spinner, you can invoke it like this to display a spinner while the command sleep 10 is running:

$ spinner sleep 10

Method 4

If you want a lowest common denominator spinner that works with /bin/sh and doesn’t rely on the extended bash parameter substitution this should work:

#!/bin/sh

# The command you are waiting on goes between the ( ) here
# The example below returns a non zero return code

(sleep 20 ; /bin/false) &

pid=$! ; i=0
while ps -a | awk '{print $1}' | grep -q "${pid}"
do
    c=`expr ${i} % 4`
    case ${c} in
       0) echo "/c" ;;
       1) echo "-c" ;;
       2) echo "\ bc" ;;
       3) echo "|c" ;;
    esac
    i=`expr ${i} + 1`
    # change the speed of the spinner by altering the 1 below
    sleep 1
    echo "bc"
done

# Collect the return code from the background process

wait ${pid}
ret=$?

# You can report on any errors due to a non zero return code here

exit ${ret}

Method 5

There are some fancy spinners and I bet every answer is spot on, especially @Jeff Schaller’s, but personally as a developer I like being able to read the code and know exactly what’s going on. I wanted a bash script to copy all my git repos into a temp zip when I start up my terminal and I also wanted a cool spinner to go with it and I’m not sure if my code is the most compact, but it definitely works well and is simple enough to read.

I’m not the most knowledgeable in bash, but I think the biggest problems are

  • running while loop in the background (could be costly to CPU, but who knows?)
  • I can still move my cursor around and that is annoying
  • and if I wanted more than one process to happen I’m not exactly sure how to go about that
function runCommand() { 
    load &                                             # calls the loading function
    local whilePID=$!                                  # gets the pid for the loop
    tar -czf ${zipFileToUpdate} ${directoryToBackUp} & # backs up files
    local backupPID=$!                                 # get's back up pid
    wait $backupPID                                    # waits for backup id
    kill $whilePID                                     # kills the while loop
    echo -ne "done"                                    # echos done and removes the spinner
}

function load() { # just a function to hold the spinner loop, here you can put whatever
    while true; do
        echo -ne "/r"
        sleep .1
        echo -ne "-r"
        sleep .1
        echo -ne " r"
        sleep .1
        echo -ne "|r"
        sleep .1
    done
}

runCommand

^ To touch on the last issue I mentioned about multiple commands, I think I would personally put all my commands in a function and then run the function in the background, but they may have a bunch of different PID’s

EXP:

function allCommands() {
    command1;
    command2;
    command3;
    ...;
}

Then in the runCommands() function

function runCommand() { 
    load &                                             # calls the loading function
    local whilePID=$!                                  # gets the pid for the loop
    allCommands &                                      # run function w/ all cmds
    local allCmdPID=$!
    ...
}

The variable allCmdPID probably won’t be the same as the commands switch and you’ll likely wait for the first command and then terminate the loading loop, all while the other commands are still running. Possible work around is:

  • getting an array of commands in for loop
  • get pid of the command
  • wait for it
  • then move on to next command

But that all seems very tedious.

Method 6

So while all the above answer work.. I thought to add mine:

DEFAULT_SpinnerFrames=("—" "\" "|" "/")

## @function: spinner(action, label, &spinnerFramesRef[])
##
## @description: Perform an action asynchronously and display
## spinner till action is completed
##
## @param action: The action the execute
## @param label: The label to display while waiting
## @param spinnerRef: In case you feel like a custom spinner, pass a ref to an array of strings
spinner() {
  local frameRef
  local action="${1}"
  local label="${2} "
  local spinnerRef="${3-DEFAULT_SpinnerFrames}"
  local spinnerFrames=$(eval "echo ${!${spinnerRef}[@]}")

  spinnerRun() {
    while true; do
      for frame in ${spinnerFrames[@]}; do
        frameRef="${spinnerRef}[${frame}]"
        echo "${label}${!frameRef}"
        tput cuu1 tput el
        sleep 0.2
      done
    done
    echo -e "r"
  }

  spinnerRun &
  local spinnerPid=$!
  ${action}
  kill "${spinnerPid}"
}

github
tests


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x