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}"
}
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