Skip to content

Instantly share code, notes, and snippets.

@marslo
Last active April 10, 2025 06:32
Show Gist options
  • Save marslo/3f4b1eae28902394ad3201d1b5ea5537 to your computer and use it in GitHub Desktop.
Save marslo/3f4b1eae28902394ad3201d1b5ea5537 to your computer and use it in GitHub Desktop.
Spinner

Tip

for ansicolor, either using \033[XXm directly, or use following c() function as below:

# credit: https://github.com/ppo/bash-colors
# author: @ppo
# shellcheck disable=SC2015,SC2059
c() { [ $# == 0 ] && printf "\e[0m" || printf "$1" | sed 's/\(.\)/\1;/g;s/\([SDIUFNHT]\)/2\1/g;s/\([KRGYBMCW]\)/3\1/g;s/\([krgybmcw]\)/4\1/g;y/SDIUFNHTsdiufnhtKRGYBMCWkrgybmcw/12345789123457890123456701234567/;s/^\(.*\);$/\\e[\1m/g'; }

Braille Patterns Spinner

1-dot

1-dot

local spinner=( '' '' '' '' '' '' '' '' )

4-dots

4-dots without color

local spinner=( '' '' '' '' '' '' '' '' '' )

4-dots with color

local spinner=(
  "$(c Rs)$(c)"
  "$(c Ys)$(c)"
  "$(c Gs)$(c)"
  "$(c Bs)$(c)"
  "$(c Ms)$(c)"
  "$(c Ys)$(c)"
  "$(c Gs)$(c)"
  "$(c Bs)$(c)"
  "$(c Ms)$(c)"
)

7-dots

7-dots without color

local spinner=( '' '' '' '' '' '' '' '' '' )

7-dots with color

local spinner=(
  "$(c Rs)$(c)"
  "$(c Ys)$(c)"
  "$(c Gs)$(c)"
  "$(c Cs)$(c)"
  "$(c Rs)$(c)"
  "$(c Ys)$(c)"
  "$(c Gs)$(c)"
  "$(c Cs)$(c)"
  )

others

spinner-3

local spinner=(
  "∙∙∙∙∙"
  "$(c Ys)$(c)∙∙∙∙"     # yellow
  "$(c Gs)$(c)∙∙∙"     # green
  "∙∙$(c Cs)$(c)∙∙"     # cyan
  "∙∙∙$(c Bs)$(c)"     # blue
  "∙∙∙∙$(c Ms)$(c)"     # magenta
)

spinner-2

local spinner=(
  "$(c Rs)∙∙∙∙∙$(c)"     # red
  "$(c Ys)●∙∙∙∙$(c)"     # yellow
  "$(c Gs)∙●∙∙∙$(c)"     # green
  "$(c Cs)∙∙●∙∙$(c)"     # cyan
  "$(c Bs)∙∙∙●∙$(c)"     # blue
  "$(c Ms)∙∙∙∙●$(c)"     # magenta
)

spinner-1

local spinner=( '∙∙∙∙∙' '●∙∙∙∙' '∙●∙∙∙' '∙∙●∙∙' '∙∙∙●∙' '∙∙∙∙●' )
#!/usr/bin/env bash
# credit: https://github.com/ppo/bash-colors
# shellcheck disable=SC2015,SC2059
c() { [ $# == 0 ] && printf "\e[0m" || printf "$1" | sed 's/\(.\)/\1;/g;s/\([SDIUFNHT]\)/2\1/g;s/\([KRGYBMCW]\)/3\1/g;s/\([krgybmcw]\)/4\1/g;y/SDIUFNHTsdiufnhtKRGYBMCWkrgybmcw/12345789123457890123456701234567/;s/^\(.*\);$/\\e[\1m/g'; }
# capture ctrl-c to exit the sub-process
function withSpinner() {
local msg="$1"; shift
local __resultvar="$1"; shift
local spinner=(
"$(c Rs)⣄$(c)"
"$(c Ys)⣆$(c)"
"$(c Gs)⡇$(c)"
"$(c Bs)⠏$(c)"
"$(c Ms)⠋$(c)"
"$(c Ys)⠹$(c)"
"$(c Gs)⢸$(c)"
"$(c Bs)⣰$(c)"
"$(c Ms)⣠$(c)"
)
local frame=0
local output
local cmdPid
local pgid=""
local interrupted=0
# explicit recovery cursor
function restoreCursor() { printf "\033[?25h" >&2; }
# ensure that any exit restores the cursor.
trap 'restoreCursor' EXIT
# hide cursor
printf "\033[?25l" >&2
printf "%s " "${msg}" >&2
set -m
trap 'interrupted=1; [ -n "${pgid}" ] && kill -TERM -- -${pgid} 2>/dev/null' INT
# shellcheck disable=SC2034,SC2030
output="$(
{
"$@" 2>/dev/null &
cmdPid=$!
pgid=$(ps -o pgid= ${cmdPid} | tr -d ' ')
echo "${pgid}" > "${tmpfile}"
while kill -0 "$cmdPid" 2>/dev/null && (( interrupted == 0 )); do
printf "\r\033[K%s %b" "${msg}" "${spinner[frame]}" >&2
((frame = (frame + 1) % ${#spinner[@]}))
sleep 0.1
done
wait "${cmdPid}" 2>/dev/null
}
)"
# \r : beginning of line
# \033[K : clear current position to end of line
if (( interrupted )); then
printf "\r\033[K\033[31m✗\033[0m Interrupted!\033[K\n" >&2
# shellcheck disable=SC2031
[ -n "${pgid}" ] && kill -TERM -- -"${pgid}" 2>/dev/null
else
printf "\r\033[K\033[32m✓\033[0m Done!\033[K\n" >&2
fi
# a separate recovery cursor is no longer required because the exit trap is handled
}
# main function
function main() {
tmpfile=$(mktemp)
trap 'rm -f "${tmpfile}"' EXIT
withSpinner "Loading..." result sleep 5
echo "Exit code: $?"
}
main "$@"
# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:
#!/usr/bin/env bash
# credit: https://github.com/ppo/bash-colors
# shellcheck disable=SC2015,SC2059
c() { [ $# == 0 ] && printf "\e[0m" || printf "$1" | sed 's/\(.\)/\1;/g;s/\([SDIUFNHT]\)/2\1/g;s/\([KRGYBMCW]\)/3\1/g;s/\([krgybmcw]\)/4\1/g;y/SDIUFNHTsdiufnhtKRGYBMCWkrgybmcw/12345789123457890123456701234567/;s/^\(.*\);$/\\e[\1m/g'; }
# capture ctrl-c to exit the sub-process
# return the sub-process stdout ( to external variable )
function withSpinner() {
local msg="$1"; shift
local __resultvar="$1"; shift
local spinner=(
"$(c Rs)⣾$(c)"
"$(c Ys)⣽$(c)"
"$(c Gs)⣻$(c)"
"$(c Cs)⢿$(c)"
"$(c Rs)⡿$(c)"
"$(c Ys)⣟$(c)"
"$(c Gs)⣯$(c)"
"$(c Cs)⣷$(c)"
)
local frame=0
local output
local cmdPid
local pgid=''
local interrupted=0
# define the cursor recovery function
restoreCursor() { printf "\033[?25h" >&2; }
# make sure that any exit restores the cursor
trap 'restoreCursor' EXIT
# hide cursor
printf "\033[?25l" >&2
printf "%s " "$msg" >&2
set -m
trap 'interrupted=1; [ -n "$pgid" ] && kill -TERM -- -$pgid 2>/dev/null' INT
# use file descriptor to capture output
local tmpout
tmpout=$(mktemp)
exec 3<> "${tmpout}"
# shellcheck disable=SC2031,SC2030
output="$(
{
# execute command and redirect output to file descriptor 3
"$@" >&3 2>/dev/null &
cmdPid=$!
pgid=$(ps -o pgid= "$cmdPid" | tr -d ' ')
# update the spinner while the command is running
while kill -0 "$cmdPid" 2>/dev/null && (( interrupted == 0 )); do
printf "\r\033[K%s %b" "${msg}" "${spinner[frame]}" >&2
((frame = (frame + 1) % ${#spinner[@]}))
sleep 0.08
done
wait "$cmdPid" 2>/dev/null
# show the captured content
cat "${tmpout}"
}
)"
# clean the temporary file
exec 3>&-
rm -f "${tmpout}"
# \r : beginning of line
# \033[K : clear current position to end of line
# shellcheck disable=SC2031
if (( interrupted )); then
printf "\r\033[K\033[31m✗\033[0m Interrupted!\033[K\n" >&2
[ -n "${pgid}" ] && kill -TERM -- -"${pgid}" 2>/dev/null
else
# or using `printf "\r" >&2` directly without sub-progress status output
printf "\r\033[K\033[32m✓\033[0m Done!\033[K\n" >&2
fi
# assign the result to an external variable
printf -v "$__resultvar" "%s" "$output"
}
function main() {
# shellcheck disable=SC2155
local tmpfile=$(mktemp)
trap 'rm -f "${tmpfile}"' EXIT
local response
withSpinner "Loading..." response \
curl -s https://<API> ...
# check curl output
echo "${response}"
}
main "$@"
# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment