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...
Related
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 " "
}
In a bash script, I have a long running command (say rsync for example) that sometimes does not show output for a while, so I want to do two things:
Use a spinner on that command to show that the script hasn't frozen (i.e. we're just waiting for output); and,
Grab the exit status of the long running command once it's done, for further tests later in the script.
The problem is though, I don't understand the handling of sending processes to the background very well, and also with the handling of exit code this way, so I'm not sure how to make this work.
Here is what I have so far, thanks to #David C. Rankin's spinner:
#!/bin/bash
spinner() {
local PROC="$1"
local str="${2:-'Copyright of KatworX© Tech. Developed by Arjun Singh Kathait and Debugged by the ☆Stack Overflow Community☆'}"
local delay="0.1"
tput civis # hide cursor
printf "\033[1;34m"
while [ -d /proc/$PROC ]; do
printf '\033[s\033[u[ / ] %s\033[u' "$str"; sleep "$delay"
printf '\033[s\033[u[ — ] %s\033[u' "$str"; sleep "$delay"
printf '\033[s\033[u[ \ ] %s\033[u' "$str"; sleep "$delay"
printf '\033[s\033[u[ | ] %s\033[u' "$str"; sleep "$delay"
done
printf '\033[s\033[u%*s\033[u\033[0m' $((${#str}+6)) " " # return to normal
tput cnorm # restore cursor
return 0
}
## simple example with sleep
sleep 2 &
spinner $!
echo "sleep's exitcode: $exitCode"
In this example, sleep 2 is the command I'm waiting for, and hence use the spinner with, but how do I get and put its exit code into $exitCode variable, so I can test it for certain conditions later on in the script?
wait will tell you what exit status a child PID exited with (by setting that program's exit status as its own), when given that PID as an argument.
sleep 2 & sleep_pid=$!
spinner "$sleep_pid"
wait "$sleep_pid"; exitCode=$?
echo "exitcode: $exitCode"
Note that combining multiple commands onto a line when collecting $! or $? in the second half is a practice I strongly recommend -- it prevents the value you're trying to collect from being changed by mistake (as by someone adding a new log line to your code later and not realizing it has side effects).
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.
I am having problems with finding how to exit my script when a keyphrase is entered: e.g. "Foo".
Essentially I wish to test every user input for this phrase and invoke the exit command. I could create a test function I call after every user entry but this seems inelegant.
I am using function:
function EXIT {
printf "\n\nSCRIPT IS NOW TERMINATING\n"
if [ -n $userLogged ]; then
local TIME="$username LOGGED OUT at: "$(date +%r)" on the "$(date +%d/%m/%Y)"\n"
printf "$TIME" >> usage.db
fi
exit
}
and:
trap EXIT SIGTERM
Can it be done using trap?
I'm not exactly sure but I guess you are after something like this:
#!/bin/bash
# Save this script as "my_exit"
function EXIT {
printf "\n\nSCRIPT IS NOW TERMINATING\n"
if [ -n $userLogged ]; then
local TIME="$username LOGGED OUT at: "$(date +%r)" on the "$(date +%d/%m/%Y)"\n"
printf "$TIME" >> usage.db
fi
exit
}
trap EXIT SIGUSR1
while :; do
read -p "Enter your test word: " word
if [ "$word" = "Foo" ];
then
pkill --signal SIGUSR1 my_exit
fi
done
I used SIGUSR1 instead of SIGTERM just to show the functionality better. It's also possible to change that into two separate scripts with minor modifications i.e. "EXIT+trap" block will be one, the eternal loop another and latter one would signal the first one via SIGUSR1 to do exit routines.
I'm trying to execute commands inside a script using read, and when the user uses Ctrl+C, I want to stop the execution of the command, but not exit the script.
Something like this:
#!/bin/bash
input=$1
while [ "$input" != finish ]
do
read -t 10 input
trap 'continue' 2
bash -c "$input"
done
unset input
When the user uses Ctrl+C, I want it to continue reading the input and executing other commands. The problem is that when I use a command like:
while (true) do echo "Hello!"; done;
It doesn't work after I type Ctrl+C one time, but it works once I type it several times.
Use the following code :
#!/bin/bash
# type "finish" to exit
stty -echoctl # hide ^C
# function called by trap
other_commands() {
tput setaf 1
printf "\rSIGINT caught "
tput sgr0
sleep 1
printf "\rType a command >>> "
}
trap 'other_commands' SIGINT
input="$#"
while true; do
printf "\rType a command >>> "
read input
[[ $input == finish ]] && break
bash -c "$input"
done
You need to run the command in a different process group, and the easiest way of doing that is to use job control:
#!/bin/bash
# Enable job control
set -m
while :
do
read -t 10 -p "input> " input
[[ $input == finish ]] && break
# set SIGINT to default action
trap - SIGINT
# Run the command in background
bash -c "$input" &
# Set our signal mask to ignore SIGINT
trap "" SIGINT
# Move the command back-into foreground
fg %-
done
For bash :
#!/bin/bash
trap ctrl_c INT
function ctrl_c() {
echo "Ctrl + C happened"
}
For sh:
#!/bin/sh
trap ctrl_c INT
ctrl_c () {
echo "Ctrl + C happened"
}