Same line bash spinner - bash

I have a spinner on the same line as some output, and when a process is done, I'd like to remove the spinner from the output and move to the next line. The part that troubles me is how to remove it when it's done.
spinner() {
local pid=$!
spin='-\|/'
i=0
while kill -0 $pid 2>/dev/null; do
i=$(((i + 1) % 4))
printf "%c" "${spin:$i:1}"
printf "\b"
sleep .1
done
}
echo "Call some process"
command &
spinner
echo "Done"
The above outputs (note that the spinner stays on the first line in its last position):
Call some process \
Done

I'd like to remove the spinner from the output and move to the next line
Overprint it with a space and then output a newline.
By the way: In my terminal the spinner runs a lot smoother if you use a single printf for %c and \b:
spinner() {
local pid=$!
local spin='-\|/'
local i=0
while kill -0 $pid 2>/dev/null; do
(( i = (i + 1) % 4 ))
printf '%c\b' "${spin:i:1}"
sleep .1
done
echo ' '
}
In case you don't want to move to a new line, but rather want to clear it immediately while keeping the cursor in the same line you can use printf ' \r' instead.

how to remove it when it's done.
So just print a space.
sleep .1
done
printf " "
}

Related

Need a shell script for spinning cursor

I need a shell script for spinning cursor which I can use with "copying" function. I tried below program and it works but the only problem I have here is that the spinner is showing below the text.
#!/bin/bash
spinner=('\' '/' '-' '\')
copy(){
echo "copying files..."
spin &
pid=$!
for i in `seq 1 10`
do
sleep 1
done
kill $pid
echo ""
}
spin(){
while [ 1 ]
do
for i in "${spinner[#]}"
do
echo -ne "\r$i"
sleep 0.2
done
done
}
copy
Expected Output: Copying files...\
#!/bin/bash
spinner () {
local chars=('|' / - '\')
# hide the cursor
tput civis
trap 'printf "\010"; tput cvvis; return' INT TERM
printf %s "$*"
while :; do
for i in {0..3}; do
printf %s "${chars[i]}"
sleep 0.3
printf '\010'
done
done
}
copy ()
{
local pid return
spinner 'Copying 5 files... ' & pid=$!
# Slow copy command here
sleep 4
return=$?
# kill spinner, and wait for the trap commands to complete
kill "$pid"
wait "$pid"
if [[ "$return" -eq 0 ]]; then
echo done
else
echo ERROR
fi
}
copy
Depending on how you use this, you probably want to hide the cursor earlier, and show it later. Instead of turning it on and off for multiple copy or spinner invocations. Eg:
#!/bin/bash
trap 'tput cvvis' EXIT
tput civis
copy
copy
# other stuff
The cursor is hidden when tput civis runs, and un-hidden (tput cvvis) when the script exits (normally, or due to interrupt etc). If you do it like this, remove the corresponding tput commands from the function (but keep the other trap commands).
The reason for hiding the cursor is that it can mess with the spinner animation.
Print the text Copying files... without trailing newline, if you don't want one:
echo -n Copying files...

How can I change output from a Bash script while waiting on user input?

I want to build a stopwatch in Bash, with a pause feature. It should display an incrementing counter, like this one does, but pause it when I hit the "p" key.
How should I implement that? If I wait for user input with read I can't refresh the counter on the screen at the same time. Putting the read inside a loop, with a timeout, is my best plan so far, but it's non-trivial to use a timeout less than one second, which is what I would need here. (It's not supported by read or GNU timeout.) Interrupts would work, but I'd like to support arbitrary keys like "p" and "x".
Is there a reasonably simple way to achieve this?
Print to console while waiting for user input
Write one function that creates the output (example with: counter or if you like spin).
Write one function to read in user commands (readCommand)
Call both functions in a loop
Set timeouts so, that key presses are read soon enough. (sleep .1 and read -t.1)
function readCommand(){
lastCommand=$1
read -t.1 -n1 c;
if [ "$c" = "p" ]
then
printf "\n\r";
return 0
fi
if [ "$c" = "g" ]
then
printf "\n\r";
return 1
fi
return $lastCommand
}
function spin(){
for i in / - \\ \| ;
do
printf "\r$i";
sleep .1;
done
}
function countUp(){
currentCount=$1
return `expr $currentCount + 1`
}
function counter(){
countUp $count
count=$?
printf "\r$count"
sleep .1;
}
command=1
count=0
while :
do
if [[ $command == 1 ]]
then
counter
fi
readCommand $command
command=$?
done
The counter will stop if user presses 'p' and go on if user presses 'g'
Simple script with file descriptor and simple input redirection, leaving no temporary files to cleanup. The waiting is done by using read parameter -t.
counter() {
while ! read -t 0.05 -n1 _; do
printf '\r\t%s' "$(date +%T.%N)"
done
}
{
IFS= read -p "Your name, Sir?"$'\n' -r name
echo >&3
} 3> >(counter "$tmp")
echo "Sir $name, we exit"
Example output:
Your name, Sir?
2:12:17.153951623l
Sir Kamil, we exit
I have made a change in the code you refer.
...
while [ true ]; do
if [ -z $(cat /tmp/pause) ]; then
STOPWATCH=$(TZ=UTC datef $DATE_INPUT $DATE_FORMAT | ( [[ "$NANOS_SUPPORTED" ]] && sed 's/.\{7\}$//' || cat ) )
printf "\r\e%s" $STOPWATCH
sleep 0.03
fi
done
So what you need to do now is a shell script that waits the "p" char from stdin and writes 1 > /tmp/pause or clean /tmp/pause to get he stopwatch paused or working.
something like:
while read char;
do
if [ $char == "p" ]; then
if [ -z $(cat /tmp/pause) ];then
echo 1 > /tmp/pause
else
echo > /tmp/pause
fi
char=0
fi
done < /dev/stdin

Collecting process ids of parallel process in bash file

Below I have a script that is collecting the process ids of individual commands, and appending them to an array in bash. For some reason as you can see stdout below, the end resulting array just contains one item, the latest id. How can the resulting PROCESS_IDS array at the end of this script contain all four process ids?
PROCESS_IDS=()
function append {
echo $1
PROCESS_IDS=("${PROCESS_IDS[#]}" $1)
}
sleep 1 && echo 'one' & append $! &
sleep 5 && echo 'two' & append $! &
sleep 1 && echo 'three' & append $! &
sleep 5 && echo 'four' & append $!
wait
echo "${PROCESS_IDS[#]}"
Here is the stdout:
83873
83875
83879
83882
three
one
four
two
83882
Don't send the append operation itself to the background. Putting an & after the content you want to background but before the append suffices: The sleep and echo are still backgrounded, but the append is not.
process_ids=( )
append() { process_ids+=( "$1" ); } # POSIX-standard function declaration syntax
{ sleep 1 && echo 'one'; } & append "$!"
{ sleep 5 && echo 'two'; } & append "$!"
{ sleep 1 && echo 'three'; } & append "$!"
{ sleep 5 && echo 'four'; } & append "$!"
echo "Background processes:" # Demonstrate that our array was populated
printf ' - %s\n' "${process_ids[#]}"
wait
My guess is that whenever you send a function call to the background, it has a copy of the global variable on its own, so they're appending the PID to four independent copies of PROCESS_IDS. That's why every function call finds that it is empty and stores a single PID in it.
http://www.gnu.org/software/bash/manual/bashref.html#Lists
If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background.
If you want to collect the outputs from all four function calls, let them write to disk and read the output at the end:
function append {
echo $1 | tee /tmp/file.$1
}
sleep 1 && echo 'one' & append $! &
sleep 5 && echo 'two' & append $! &
sleep 1 && echo 'three' & append $! &
sleep 5 && echo 'four' & append $!
wait
cat /tmp/file.*
Edit: this is just a proof of concept - don't do it with file system anyway (as William pointed out, this is going to be error prone unless you take care of uniqueness and synchronization). I only wanted to illustrate that you need to find another way of getting the information out of the subshells.

bash script overwrite current line completely

I have been playing with a script to test the speed of various VoIP servers by pinging, I then found a progress bar script and incorporated that... because cool!
now I'm trying to display the current server being tested below the status bar, I have the line overwriting but if the next server name is shorter it does not overwrite completely. I have tried various suggestions I've found but non work or they screw up my progress bar.
Im running osx but may also use this on various linux distros.
any suggestions would be great!
#!/bin/bash
HOSTS=("atlanta.voip.ms" "atlanta2.voip.ms" "chicago.voip.ms" "chicago2.voip.ms" "chicago3.voip.ms" "chicago4.voip.ms" "dallas.voip.ms" "denver.voip.ms" "denver2.voip.ms" "houston.voip.ms" "houstonnew1.voip.ms" "houstonnew2.voip.ms" "losangeles.voip.ms" "losangeles2.voip.ms" "newyork.voip.ms" "newyork2.voip.ms" "newyork3.voip.ms" "newyork4.voip.ms" "sanjose.voip.ms" "sanjose2.voip.ms" "seattle.voip.ms" "seattle2.voip.ms" "seattle3.voip.ms" "tampa.voip.ms" "tampanew1.voip.ms" "tampanew2.voip.ms" "washington.voip.ms" "washington2.voip.ms" "montreal.voip.ms" "montreal2.voip.ms" "montreal3.voip.ms" "montreal4.voip.ms" "toronto.voip.ms" "toronto2.voip.ms" "toronto3.voip.ms" "toronto4.voip.ms" "vancouver.voip.ms" "vancouver2.voip.ms" "amsterdam.voip.ms" "london.voip.ms" "melbourne.voip.ms" "paris.voip.ms")
Smallest="200000"
Server=""
tLen=${#HOSTS[#]}
# Slick Progress Bar
# Created by: Ian Brown (ijbrown#hotmail.com)
# Please share with me your modifications
# Functions
PUT(){ echo -en "\033[${1};${2}H";}
DRAW(){ echo -en "\033%";echo -en "\033(0";}
WRITE(){ echo -en "\033(B";}
HIDECURSOR(){ echo -en "\033[?25l";}
NORM(){ echo -en "\033[?12l\033[?25h";}
function showBar {
percDone=$(echo 'scale=2;'$1/$2*100 | bc)
halfDone=$(echo $percDone/2 | bc)
barLen=$(echo ${percDone%'.00'})
halfDone=`expr $halfDone + 6`
tput bold
PUT 7 28; printf "%4.4s " $barLen%
PUT 5 $halfDone; echo -e "\033[7m \033[0m"
tput sgr0
}
# Start Script
clear
HIDECURSOR
echo -e ""
echo -e ""
DRAW
echo -e " PLEASE WAIT WHILE SCRIPT IS IN PROGRESS"
echo -e " lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk"
echo -e " x x"
echo -e " mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj"
echo -e ""
echo -e ""
WRITE
for (( i=0; i<${tLen}; i++ ))
do
showBar $i ${tLen}
serl=${HOSTS[$i]}
seru=$(echo "$serl" | tr '[:lower:]' '[:upper:]')
echo ""
echo ""
echo ""
echo ""
echo ""
echo ""
echo ""
#this line needs to overwrite completley
echo -ne "" '\r " TESTING:" $seru
Current1=` ping -c 4 -q -i .2 ${HOSTS[$i]} | grep avg | awk -F'/' '{print $5 }'`
Current=${Current1/./}
if [ "$Current" -lt "$Smallest" ]
then
Server=${HOSTS[$i]}
Smallest=$Current
fi
done
clear
Smallestd=$(echo "$Smallest" | sed 's/...$/.&/')
echo "Fastest Server = $Server # $Smallestd ms"
Here is an example of controlling clearing to end-of-line both during display of the meter, and in restoring the cursor after completion. I have reversed the loop to show the meter progressing from 100% (full) to 1% cleaning up after itself as it goes:
#!/bin/bash
## string of characters for meter (60 - good for 120 char width)
str='▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒'
tput civis # make cursor invisible
for i in `seq 100 -1 1`; do # for 1 to 100, save cursor, restore, output, restore
printf "\033[s\033[u Progress: %s %3d %% \033[u" "${str:0:$(((i+1)/2))}" "$i"
sleep 0.1 # small delay
done
sleep 0.5
printf "\033[K" # clear to end-of-line
tput cnorm # restore cursor to normal
exit 0
Note: cursor control and clearing during display of the meter is provided by the ANSI escapes for save cursor position and restore cursor position. After completion, clear to end of line is used to clean up. tput is used to control cursor visibility, but can also be used to save, restore and clear to end of line.
You can pad your with spaces so you always write the same number of characters. That way you'll overwrite the extra characters from before with spaces. For example you could something like
echo -en "\r"; echo -n $(printf " TESTING: %-40s" $seru)
Thanks to David C. Rankin I have an easy working answer
echo -ne "" '\r' " TESTING:" $seru '\033[K'

When using read with the -t flag, can I display the time remaining?

I have an input that times out after a few seconds, but to make it less jarring I would like it to display the time remaining. How can I do this?
I don't think it can be done with read -t, but this script accomplishes a countdown on another line as a background process:
#!/bin/bash
function displayCountdown {
((remaining=$1))
while [ $remaining -gt 0 ]
do
tput sc # save cursor pos
tput cuu1 # go up one line
tput cub 80 # go 80 chars left
tput el # clear to eol
( echo -n "$remaining second(s) remaining" ) >&2
tput rc # restore saved cursor pos
((remaining=remaining-1))
sleep 1
done
echo
}
NUM_SECONDS=5
## the first echo is needed
echo ; displayCountdown $NUM_SECONDS & read -t $NUM_SECONDS ; RC=$? ; kill -9 $! ; wait $! 2>/dev/null
echo "Got ($RC): $REPLY"
exit 0
It's not perfect. For instance, when pressing the delete key, it sometimes produces ^R's and messes up the terminal a bit. But, if you decide to use this, maybe there is some stty setting you can use to remedy this.
I tried this on a Mac. I haven't tried this on Linux.

Resources