I found this for BASH, but I want to do the same thing with shell (#!/bin/sh
).
The twist is to make it a timer also, so like wait 60 seconds for example until it ends.
I found this for BASH, but I want to do the same thing with shell (#!/bin/sh
).
The twist is to make it a timer also, so like wait 60 seconds for example until it ends.
Fadi's own solution is helpful (and comes by courtesy of Adam Katz's comment on the linked answer), but comes with 2 caveats:
\r
, only works at the beginning of a line.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.
done
done
The above, due to printing the \b
char. after the spinner char., displays the cursor behind the spinner char; if that is aesthetically undesirable, use the following variation to display the cursor on top of the spinner:
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
done
done
I0_ol suggests using tput civis
and tput cnorm
to hide the cursor temporarily; while not strictly POSIX-compliant (POSIX mandates only 3 tput
operands: clear
, init
, and reset
), it does appear to be supported by most modern terminal emulators.
printf 'Processing: '
tput civis # Hide cursor.
# To be safe, ensure that the cursor is turned back on when
# the script terminates, for whatever reason.
trap 'tput cnorm' EXIT
while :; do
for c in / - \\ \|; do
printf '%s\b' "$c"
[ -f results ] && { printf '\n'; break 2; }
sleep 1
done
done
tput cnorm # Show cursor again.
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 ))
done
done
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.
The solution is this:
#!/bin/sh
i=0
while [ $i -le 60 ]; do
for s in / - \\ \|; do
printf "\r$s"
sleep .1
done
i=$((i+1))
done