Spinner in Shell

2019-09-11 06:09发布

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.

2条回答
做个烂人
2楼-- · 2019-09-11 06:18

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
查看更多
老娘就宠你
3楼-- · 2019-09-11 06:32

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.
  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.
查看更多
登录 后发表回答