How to abort a bash script in n seconds [duplicate] - bash

This question already has answers here:
Timeout a command in bash without unnecessary delay
(24 answers)
Closed 8 years ago.
I want to make my script wait for n seconds, during which user can abort the script. My code till now looks like this
echo "Start Date provided :" $STARTDATE
echo "End date provided :" $ENDDATE
echo ""
echo "Please check the dates provided and in case of error press CTRL+C"
seq 1 15 |while read i; do echo -ne "\rWaiting for $i seconds"; sleep 1; done
echo ""
echo "Executing query on the remote side, please wait..."
When the user presses ctrl+c though what happens is that the while loop ends and continues executing the rest of the script.
How can I make it abort the entire script? Thanks in advance for any advise

I would use trap
#!/bin/bash
trap 'echo -e "\nBye"; exit 1' INT
echo "Please check the dates provided and in case of error press CTRL+C"
seq 1 15 |while read i; do echo -ne "\rWaiting for $i seconds"; sleep 1; done
echo "Executing query on the remote side, please wait..."

Here's a simpler program that reproduces your problem:
true | sleep 10 # Hit Ctrl-C here
echo "why does this execute?"
The problem is that bash determines whether or not to continue the script based on whether the SIGINT was handled. It determines whether the SIGINT was handled based on whether or not the process it's currently running is killed by this signal.
Since seq prints its numbers and exits immediately and successfully, it's not killed by SIGINT. bash therefore incorrectly assumes the signal was handled, and it keeps going.
You can solve this in two ways:
Add a SIGINT handler that exits your script regardless of whether the signal appears to be handled.
Don't use seq or anything else that immediately exits.
For the first approach, see user3620917's answer.
The second approach,
for i in {15..1}
do
printf '\rYou have %d seconds to hit Ctrl-C' "$i"
sleep 1
done

To catch CTRL+C you can use trap. For example:
#!/bin/bash
trap 'got_one' 2
got_one() {
echo "I got one"
exit 69
}
echo -e "PID: $$, PPID: $PPID"
sleep 100
So your script can look like this:
#!/bin/bash
trap "interrupt" SIGINT
interrupt() {
echo -e "\nExecution canceled."
exit 69
}
countdown() {
duration=$1 # in seconds
seq $duration -1 0|while read i; do echo -ne "\rWaiting for $i seconds"; sleep 1; done
}
STARTDATE="foo"
ENDDATE="bar"
cat <<END
Start Date provided: $STARTDATE
End date provided: $ENDDATE
Please check the dates provided and in case of error press CTRL+C
END
countdown 15
echo -e "\nExecuting query on the remote side, please wait..."

Related

sleep like function with user input intterupt

I wanted to do a shell function which countdown to n seconds (10 for exemple) and after that, continue the script.
I tried with the sleep function but it does stop the script entirely.
I want something like when the user input "y" during this countdown, it will stop the countdown and do something particular (much like an "interrupt").
And if the countdown finishes without any user input, the script continues.
Thank you !
**UPDATE* *
#Krzysztof Księżyk That's exactly what i wanted !
One difference, if i want the read return only if "Y" is the input how can i do that ? i already tried with the -d and -a...
Here is my code :
label="NTFS"
read -n 1 -t 5 MS
if [ -z "$MS" ]
then
echo "You don't input value, default will be taken"
else
echo -e "\nYou pressed 'Y' and want change default backup device."
read -p "Please input label of your secondary backup device: " secondary_label
label=$secondary_label
fi
echo "the choosen backup device label is $label"
You can use read command, eg.
read -n 1 -t 10
It will wait up to 10 second just for 1 character.
** UPDATE **
modified solution based on extra info from author
CNT=0
while [ $CNT -lt 10 ]; do
(( CNT++ ))
read -s -n 1 -t 1 -r KEY
if [ "$KEY" == 'y' ]; then
echo 'doing something'
else
echo 'doing nothing'
fi
done
If you want an interrupt, you probably want to trap SIGINT:
#!/bin/sh
trap : SIGINT
echo begin
for((i=0;i<10;i++)); do printf "$i\r"; sleep 1; done &
wait || kill $!
echo
echo end
The script counts to 10, but the timer aborts when the user sends a SIGINT (eg ctrl-C)

Bash skip sleep and go to next loop iteration

I have a bash loop that looks like this:
something(){
echo "something"
}
while true; do
something
sleep 10s
done | otherCommand
When the loop is in the sleep state, I want to be able to be able to run a function from the terminal that will skip the sleep step and go on to the next iteration of the loop.
For example, if the script has been sleeping for 5 seconds, I want the command to stop the script from sleeping, go on to the next iteration of the loop, run something, and continue sleeping again.
This is not foolproof, but may be robust enough:
To interrupt the sleep command, run:
pkill -f 'sleep 10s'
Note that the script running the sleep command prints something like the following to stderr when sleep is killed: <script-path>: line <line-number>: <pid> Terminated: 15 sleep 10s. Curiously, you cannot suppress this script with sleep 10s 2>/dev/null; to suppress it, you have to either apply 2>/dev/null to the while loop as a whole, or to the script invocation as a whole.
As #miken32 points out in the comments, this command tries to kill all commands whose invocation command line matches sleep 10s, though - unless you're running as root - killing will only succeed for matches among your processed due to lack of permission to kill other users' processes.
To be safer, you can explicitly restrict matches to your own processes:
pkill -u "$(id -u)" -f 'sleep 10s'
Truly safe use, however, requires that you capture your running script's PID (process ID), save it to a file, and use the PID from that file with pkill's -P option, which limits matches to child processes of the specified PID - see this answer (thanks, #miken32).
If you want to skip the sleep, use something like a file you can touch:
if [ ! -f /tmp/skipsleep ]; then
sleep 10
fi
When you want to interrupt the sleep command, kill it!
You could read from a named pipe with read -t 10 instead of sleeping: props for named pipe added by "that other guy"
something()
{
echo "something"
}
somethingelse()
{
echo "something else"
}
mkfifo ~/.myfifo
while cat ~/.myfifo; do true; done |
while true
do
something
read -t 10 && somethingelse
done
Now whenever another script writes to the fifo with echo > ~/.myfifo, the loop will skip its current wait and continue to the next iteration.
This way, different users or different scripts waiting ten seconds will not interfere with each other.
Solution below works if script is running in foreground and you are waiting. Of course you would need a valid loop exit condition. You can also check the value entered and act on it differently. In this case pressing any key except 'j' will iterate the loop. Pressing 'j' will pipe the output of somethingelse to awk.
something()
{
echo "something"
}
somethingelse()
{
echo "something else"
}
while true; do
something |awk '{print "piping something: " $0 }'
read -t 3 -s -n 1 answer
if [ $? == 0 ]; then
echo "you didn't want to wait!"
fi
if [ "$answer" = "j" ]; then
somethingelse | awk '{print "piping something else: " $0 }'
fi
done
If its interactive then just use "read -t #" instead of "sleep #".
You can then just press enter to skip the timeout.
This may be old, but a very simple, elegant solution is to iterate a non existant, empty or 0 set variable, while checking if it has some value first. In this case the ternary operator works as intended
for((;;)){
# skip an interation
(( i ))&& do something || i=1
# skip 5 iterations
(( n >= 5 ))&& do something ||
((n++))
}

Customized progress message for tasks in bash script

I'm currently writing a bash script to do tasks automatically. In my script I want it to display progress message when it is doing a task.
For example:
user#ubuntu:~$ Configure something
->
Configure something .
->
Configure something ..
->
Configure something ...
->
Configure something ... done
All the progress message should appear in the same line.
Below is my workaround so far:
echo -n "Configure something "
exec "configure something 2>&1 /dev/null"
//pseudo code for progress message
echo -n "." and sleep 1 if the previous exec of configure something not done
echo " done" if exec of the command finished successfully
echo " failed" otherwise
Will exec wait for the command to finish and then continue with the script lines later?
If so, then how can I echo message at the same time the exec of configure something is taking place?
How do I know when exec finishes the previous command and return true? use $? ?
Just to put the editorial hat on, what if something goes wrong? How are you, or a user of your script going to know what went wrong? This is probably not the answer you're looking for but having your script just execute each build step individually may turn out to be better overall, especially for troubleshooting. Why not define a function to validate your build steps:
function validateCmd()
{
CODE=$1
COMMAND=$2
MODULE=$3
if [ ${CODE} -ne 0 ]; then
echo "ERROR Executing Command: \"${COMMAND}\" in Module: ${MODULE}"
echo "Exiting."
exit 1;
fi
}
./configure
validateCmd $? "./configure" "Configuration of something"
Anyways, yes as you probably noticed above, use $? to determine what the result of the last command was. For example:
rm -rf ${TMP_DIR}
if [ $? -ne 0 ]; then
echo "ERROR Removing directory: ${TMP_DIR}"
exit 1;
fi
To answer your first question, you can use:
echo -ne "\b"
To delete a character on the same line. So to count to ten on one line, you can do something like:
for i in $(seq -w 1 10); do
echo -en "\b\b${i}"
sleep .25
done
echo
The trick with that is you'll have to know how much to delete, but I'm sure you can figure that out.
You cannot call exec like that; exec never returns, and the lines after an exec will not execute. The standard way to print progress updates on a single line is to simply use \r instead of \n at the end of each line. For example:
#!/bin/bash
i=0
sleep 5 & # Start some command
pid=$! # Save the pid of the command
while sleep 1; do # Produce progress reports
printf '\rcontinuing in %d seconds...' $(( 5 - ++i ))
test $i -eq 5 && break
done
if wait $pid; then echo done; else echo failed; fi
Here's another example:
#!/bin/bash
execute() {
eval "$#" & # Execute the command
pid=$!
# Invoke a shell to print status. If you just invoke
# the while loop directly, killing it will generate a
# notification. By trapping SIGTERM, we suppress the notice.
sh -c 'trap exit SIGTERM
while printf "\r%3d:%s..." $((++i)) "$*"; do sleep 1
done' 0 "$#" &
last_report=$!
if wait $pid; then echo done; else echo failed; fi
kill $last_report
}
execute sleep 3
execute sleep 2 \| false # Execute a command that will fail
execute sleep 1

Set call file to hang up after some time using Bash AGI

I'm trying to make a call using a call file on asterisk where it plays a file and hangs up after a given time whether the sound file has finished or not.
I have it working with this bash script as the AGI script:
#!/bin/bash
Duration="30"
file="sound.mp3"
while read VAR && [ -n ${VAR} ] ; do : ; done
echo "ANSWER"
read RESPONSE
echo 'SET AUTOHANGUP $Duration'
echo 'EXEC PLAYBACK "'$file'" ""'
read RESPONSE
exit 0
The problem is that the asterisk cdr logs show the call to last 30 seconds whether the the person on the other end has hung up or not...
Can anyone help?
OK this is embarrassing...
I cleaned up my code so that I could paste it on here and when I try again it seems to work how I want. Still don't know what then problem was but this code seems to work
#!/bin/bash
Duration="20"
file="soundfile"
while read VAR && [ -n ${VAR} ] ; do : ; done
echo "ANSWER"
read RESPONSE
echo 'SET AUTOHANGUP '$Duration''
echo 'EXEC PLAYBACK "'$file'" ""'
read RESPONSE
exit 0
You can use the signal facility. For example:
#!/bin/bash
times_up(){ echo "time is up, pens down."; exit 0; }
trap 'times_up' ALRM
(sleep 10;kill -0 $$ && kill -ALRM $$)&
s=0
while (true) do
sleep 1
echo "$s: do my stuff"
let s=$s+1
done

How to retry a command in Bash?

I have a command that should take less than 1 minute to execute, but for some reason has an extremely long built-in timeout mechanism. I want some bash that does the following:
success = False
try(my_command)
while(!(success))
wait 1 min
if my command not finished
retry(my_command)
else
success = True
end while
How can I do this in Bash?
Look at the GNU timeout command. This kills the process if it has not completed in a given time; you'd simply wrap a loop around this to wait for the timeout to complete successfully, with delays between retries as appropriate, etc.
while timeout -k 70 60 -- my_command; [ $? = 124 ]
do sleep 2 # Pause before retry
done
If you must do it in pure bash (which is not really feasible - bash uses lots of other commands), then you are in for a world of pain and frustration with signal handlers and all sorts of issues.
Please expand on your answer a little. -k 70 is --kill-after= 70 seconds, 124 exit on timeout; what is the 60?
The linked documentation does explain the command; I don't really plan to repeat it all here. The synopsis is timeout [options] duration command [arg]...; one of the options is -k duration. The -k duration says "if the command does not die after the SIGTERM signal is sent at 60 seconds, send a SIGKILL signal at 70 seconds" (and the command should die then). There are a number of documented exit statuses; 124 indicates that the command timed out; 137 that it died after being sent the SIGKILL signal, and so on. You can't tell if the command itself exits with one of the documented statuses.
I found a script from:
http://fahdshariff.blogspot.com/2014/02/retrying-commands-in-shell-scripts.html
#!/bin/bash
# Retries a command on failure.
# $1 - the max number of attempts
# $2... - the command to run
retry() {
local -r -i max_attempts="$1"; shift
local -i attempt_num=1
until "$#"
do
if ((attempt_num==max_attempts))
then
echo "Attempt $attempt_num failed and there are no more attempts left!"
return 1
else
echo "Attempt $attempt_num failed! Trying again in $attempt_num seconds..."
sleep $((attempt_num++))
fi
done
}
# example usage:
retry 5 ls -ltr foo
I liked #Jonathan's answer, but tried to make it more straight forward for future use:
until timeout 1 sleep 2
do
echo "Happening after 1s of sleep"
done
Adapting #Shin's answer to use kill -0 rather than jobs so that this should work even with classic Bourne shell, and allow for other background jobs. You may have to experiment with kill and wait depending on how my_command responds to those.
while true ; do
my_command &
sleep 60
if kill -0 $! 2>/dev/null; then
# Job took too long
kill $!
else
echo "Job is done"
# Reap exit status
wait $!
break
fi
done
You can run a command and retain control with the & background operator. Run your command in the background, sleep for as long as you wish in the foreground, and then, if the background job hasn't terminated, kill it and start over.
while true ; do
my_command &
sleep 60
if [[ $(jobs -r) == "" ]] ; then
echo "Job is done"
break
fi
# Job took too long
kill -9 $!
done
# Retries a given command given number of times and outputs to given variable
# $1 : Command to be passed : handles both simple, piped commands
# $2 : Final output of the command(if successfull)
# $3 : Number of retrial attempts[Default 5]
function retry_function() {
echo "Command to be executed : $1"
echo "Final output variable : $2"
echo "Total trials [Default:5] : $3"
counter=${3:-5}
local _my_output_=$2 #make sure passed variable is not same as this
i=1
while [ $i -le $counter ]; do
local my_result=$(eval "$1")
# this tests if output variable is populated and accordingly retries,
# Not possible to provide error status/logs(STDIN,STDERR)-owing to subshell execution of command
# if error logs are needed, execute the same code, outside function in same shell
if test -z "$my_result"
then
echo "Trial[$i/$counter]: Execution failed"
else
echo "Trial[$i/$counter]: Successfull execution"
eval $_my_output_="'$my_result'"
break
fi
let i+=1
done
}
retry_function "ping -c 4 google.com | grep \"min/avg/max\" | awk -F\"/\" '{print \$5}'" avg_rtt_time
echo $avg_rtt_time
- To pass in a lengthy command, pass a method echoing the content. Take care of method expansion accordingly in a subshell at appropriate place.
- Wait time can be added too - just before the increment!
- For a complex command, youll have to take care of stringifying it(Good luck)

Resources