Bash wait terminates immediately? - bash

I want to play a sound after a command finishes, but only if the command took more than a second.
I have this code (copied from https://stackoverflow.com/a/11056286/1757964 and modified slightly):
( $COMMAND ) & PID=$!
( sleep 1; wait -f $PID 2>/dev/null; paplay "$SOUND" ) 2>/dev/null & WATCH=$!
wait -f $PID 2>/dev/null && pkill -HUP -P $WATCH
The "wait" on the second line seems to terminate immediately, though. I tried the -f flag but the behavior didn't change. How can I make the "wait" actually wait?

The problem is that you're running wait in a subshell. A shell can only wait for its own children, but the process you're trying to wait for is a sibling of the subshell, not a child.
There's no need to use wait for this. The question you copied the code from is for killing a process if it takes more than N seconds, not for telling how long a command took.
You can use the SECONDS variable to tell if it took more than a second. This variable contains the number of seconds since the shell started, so just check if it has increased.
start=$SECONDS
$COMMAND
if (($SECONDS > $start))
then
paplay "$SOUND"
fi

I'd probably want to streamline this to capture the data (in # of seconds since epoch) and then compare the difference (and if > 1 second then play sound), eg:
prgstart=$(date '+%s') # grab current time in terms of # of seconds since epoch
$COMMAND # run command in foreground => no need for sleep/wait/etc; once it completes ...
prgend=$(date '+$s') # grab current time in terms of # of seconds since epoch
if [[ $(( ${prgend} - ${prgstart} )) -gt 1 ]]
then
paplay "$SOUND"
fi

Related

Sleep seems to run first in bash script

I need to terminate a script if it exceeds a specific duration (10 mins)
examplescript.sh &
pid=$!
sleep 600
if ['pgrep $pid']
then
kill $pid
fi
When I tested it on my test environment, it seems working well. examplescript.sh runs first and if it runs for more than 10 mins, it will be terminated. However, when I tried in our production environment, it seems that sleep runs first. It waits 600s before running the examplescript.sh. Is there something wrong in the script?
There is multiply thing you should correct in your code.
pgrep will make a regex search on process names not pids. You can use kill -0 pid to check if a process with pid is running.
[ (test) is a command[1] and should be treated as one. That means each argument should be separated by spaces. When using [ the last argument should also be ]:
[ arg1 arg2 ]
In your example you wont need [ since kill -0 will exit truly if the process is still running:
if kill -0 pid; then
And to wrap it up:
examplescript.sh &
pid=$!
sleep 600
if kill -0 "$pid" 2> /dev/null; then
kill "$pid"
fi
kill -0 will write an error to stderr if the process is not running anymore. So we redirect that to /dev/null.
[1] It's usually a build-in these days.
Another thing to note is that your script will run for 600 seconds even though examplescript.sh will only take a few seconds to run.
Are your production machines significantly faster? I do not have example script to really run this on my machine, but I think your problem might be solved if you take the code you mention above
examplescript.sh &
pid=$!
sleep 600
if ['pgrep $pid']
then
kill $pid
fi
put it in a file called, say, monitor.sh and run that file in the background. i.e.
monitor.sh &
Hope this helps.

shell script - how to stop "watch" command in the shell script [duplicate]

I have a bash script that launches a child process that crashes (actually, hangs) from time to time and with no apparent reason (closed source, so there isn't much I can do about it). As a result, I would like to be able to launch this process for a given amount of time, and kill it if it did not return successfully after a given amount of time.
Is there a simple and robust way to achieve that using bash?
P.S.: tell me if this question is better suited to serverfault or superuser.
(As seen in:
BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")
If you don't mind downloading something, use timeout (sudo apt-get install timeout) and use it like: (most Systems have it already installed otherwise use sudo apt-get install coreutils)
timeout 10 ping www.goooooogle.com
If you don't want to download something, do what timeout does internally:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )
In case that you want to do a timeout for longer bash code, use the second option as such:
( cmdpid=$BASHPID;
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &
or to get the exit codes as well:
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?
sleep 999&
t=$!
sleep 10
kill $t
I also had this question and found two more things very useful:
The SECONDS variable in bash.
The command "pgrep".
So I use something like this on the command line (OSX 10.9):
ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done
As this is a loop I included a "sleep 0.2" to keep the CPU cool. ;-)
(BTW: ping is a bad example anyway, you just would use the built-in "-t" (timeout) option.)
Assuming you have (or can easily make) a pid file for tracking the child's pid, you could then create a script that checks the modtime of the pid file and kills/respawns the process as needed. Then just put the script in crontab to run at approximately the period you need.
Let me know if you need more details. If that doesn't sound like it'd suit your needs, what about upstart?
One way is to run the program in a subshell, and communicate with the subshell through a named pipe with the read command. This way you can check the exit status of the process being run and communicate this back through the pipe.
Here's an example of timing out the yes command after 3 seconds. It gets the PID of the process using pgrep (possibly only works on Linux). There is also some problem with using a pipe in that a process opening a pipe for read will hang until it is also opened for write, and vice versa. So to prevent the read command hanging, I've "wedged" open the pipe for read with a background subshell. (Another way to prevent a freeze to open the pipe read-write, i.e. read -t 5 <>finished.pipe - however, that also may not work except with Linux.)
rm -f finished.pipe
mkfifo finished.pipe
{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!
# Get command PID
while : ; do
PID=$( pgrep -P $SUBSHELL yes )
test "$PID" = "" || break
sleep 1
done
# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &
read -t 3 FINISHED <finished.pipe
if [ "$FINISHED" = finished ] ; then
echo 'Subprocess finished'
else
echo 'Subprocess timed out'
kill $PID
fi
rm finished.pipe
Here's an attempt which tries to avoid killing a process after it has already exited, which reduces the chance of killing another process with the same process ID (although it's probably impossible to avoid this kind of error completely).
run_with_timeout ()
{
t=$1
shift
echo "running \"$*\" with timeout $t"
(
# first, run process in background
(exec sh -c "$*") &
pid=$!
echo $pid
# the timeout shell
(sleep $t ; echo timeout) &
waiter=$!
echo $waiter
# finally, allow process to end naturally
wait $pid
echo $?
) \
| (read pid
read waiter
if test $waiter != timeout ; then
read status
else
status=timeout
fi
# if we timed out, kill the process
if test $status = timeout ; then
kill $pid
exit 99
else
# if the program exited normally, kill the waiting shell
kill $waiter
exit $status
fi
)
}
Use like run_with_timeout 3 sleep 10000, which runs sleep 10000 but ends it after 3 seconds.
This is like other answers which use a background timeout process to kill the child process after a delay. I think this is almost the same as Dan's extended answer (https://stackoverflow.com/a/5161274/1351983), except the timeout shell will not be killed if it has already ended.
After this program has ended, there will still be a few lingering "sleep" processes running, but they should be harmless.
This may be a better solution than my other answer because it does not use the non-portable shell feature read -t and does not use pgrep.
Here's the third answer I've submitted here. This one handles signal interrupts and cleans up background processes when SIGINT is received. It uses the $BASHPID and exec trick used in the top answer to get the PID of a process (in this case $$ in a sh invocation). It uses a FIFO to communicate with a subshell that is responsible for killing and cleanup. (This is like the pipe in my second answer, but having a named pipe means that the signal handler can write into it too.)
run_with_timeout ()
{
t=$1 ; shift
trap cleanup 2
F=$$.fifo ; rm -f $F ; mkfifo $F
# first, run main process in background
"$#" & pid=$!
# sleeper process to time out
( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
read sleeper <$F
# control shell. read from fifo.
# final input is "finished". after that
# we clean up. we can get a timeout or a
# signal first.
( exec 0<$F
while : ; do
read input
case $input in
finished)
test $sleeper != 0 && kill $sleeper
rm -f $F
exit 0
;;
timeout)
test $pid != 0 && kill $pid
sleeper=0
;;
signal)
test $pid != 0 && kill $pid
;;
esac
done
) &
# wait for process to end
wait $pid
status=$?
echo finished >$F
return $status
}
cleanup ()
{
echo signal >$$.fifo
}
I've tried to avoid race conditions as far as I can. However, one source of error I couldn't remove is when the process ends near the same time as the timeout. For example, run_with_timeout 2 sleep 2 or run_with_timeout 0 sleep 0. For me, the latter gives an error:
timeout.sh: line 250: kill: (23248) - No such process
as it is trying to kill a process that has already exited by itself.
#Kill command after 10 seconds
timeout 10 command
#If you don't have timeout installed, this is almost the same:
sh -c '(sleep 10; kill "$$") & command'
#The same as above, with muted duplicate messages:
sh -c '(sleep 10; kill "$$" 2>/dev/null) & command'

Checking and killing hanged background processes in a bash script

Say I have this pseudocode in bash
#!/bin/bash
things
for i in {1..3}
do
nohup someScript[i] &
done
wait
for i in {4..6}
do
nohup someScript[i] &
done
wait
otherThings
and say this someScript[i] sometimes end up hanging.
Is there a way I can take the process IDs (with $!)
and check periodically if the process is taking more than a specified amount of time after which I want to kill the hanged processes with kill -9 ?
Unfortunately the answer from #Eugeniu did not work for me, timeout gave an error.
However I found useful doing this routine, I'll post it here so anyone can take advantage of it if in my same problem.
Create another script which goes like this
#!/bin/bash
#monitor.sh
pid=$1
counter=10
while ps -p $pid > /dev/null
do
if [[ $counter -eq 0 ]] ; then
kill -9 $pid
#if it's still there then kill it
fi
counter=$((counter-1))
sleep 1
done
then in the main work you just put
things
for i in {1..3}
do
nohup someScript[i] &
./monitor.sh $! &
done
wait
In this way for any of your someScript you will have a parallel process that checks if it's still there every chosen interval (until maximum time decided by the counter) and that actually quit itself if the associated process dies (or gets killed)
One possible approach:
#!/bin/bash
# things
mypids=()
for i in {1..3}; do
# launch the script with timeout (3600s)
timeout 3600 nohup someScript[i] &
mypids[i]=$! # store the PID
done
wait "${mypids[#]}"

get the number of seconds left for the sleep command to end in a shell script

I built a shell script that sleeps for a specified amount of minutes and shows notification when it is done.
TIME=$(zenity --scale --title="Next Session in (?) minutes")
sleep $TIME'm'
BEEP="/usr/share/sounds/freedesktop/stereo/complete.oga"
paplay $BEEP
notify-send "Next Session" "Press <Ctrl><Shift><s> to run the script again"
I prevented multiple instance of the program from executing using a file based approach at the beginning of the code. When a user wants to run the script while another instance is running, it shows a notification that the script is already running.
LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
notify-send "Already Running" $SECONDS
exit
fi
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}
and finally remove the temporary file at the end of the script
rm -f ${LOCKFILE}
Now I want to add a text to the notification that tells how many seconds are left for the sleep command in my shell script to end. (changing the already running notification as follows)
notify-send "Already Running" $SECONDS
To implement the sleep command with my own controlled while loop would affect the overall performance of the computer. I think the sleep command is a better option as it optimizes the process by sending itself to a waiting state in the process queue.
Is there any way I can go around the problem?
Store the time when the script is supposed to end in the lock file.
if [ -e "$LOCKFILE" ]; then
read pid endtime < "$LOCKFILE"
if kill -0 "$pid"; then
notify-send "Already running" $(($(date +%s) - $endtime))
exit
fi
fi
trap "rm -f ${LOCKFILE}" EXIT # Use cascaded trap
trap 'exit 127' INT TERM
echo $$ $(($(date +%s) + (60 * $TIME))) >"$LOCKFILE"
There is a race condition here; if two scripts are started at almost the same time, the first could be inside the if but before the echo when the second starts. If you really need to prevent that, use a lock directory instead of a file -- directory creation is atomic, and either succeeds or fails at just a single point in time (but then you'll need to clean out the stale directory in the mystery scenario where the directory exists but is not owned by a file -- maybe after a careless OOM killer or something).
I think Triplee has a fine answer, another way to handle it that can be applied to any running process that may block is to bg the process briefly to grab and save the assigned pid $! to a file then fg the process back.
From there you can do the math and get the seconds via ps:
TIME=$(zenity --scale --title="Next Session in (?) minutes")
SLEEP_PID_FILE="/tmp/__session_ui_sleep_pid__"
sleep $TIME'm' &
echo $! >> "${SLEEP_PID_FILE}"
fg
BEEP="/usr/share/sounds/freedesktop/stereo/complete.oga"
paplay $BEEP
notify-send "Next Session" "Press <Ctrl><Shift><s> to run the script again"
Then afterward you can find the current elapsed time with something like:
notify-send "Already running for $(($(date +%s)-$(date -d"$(ps -o lstart= -p$(< "${SLEEP_PID_FILE}"))" +%s))) seconds..."

Shell Script (bash/ksh): 20 seconds to read a variable

I need to wait for an input for 20 seconds, after that myscript should continue the execution.
I've tried using read -t20 var however this works only on bash. I'm using ksh on Solaris 10.
Can someone help me please?
EDIT: 20 seconds is only an example. Let's pretend it needs to wait for 1 hour. But the guy could or could not be in front the PC to write the input, he doesn't need to wait the 1 hour to enter an input, but if he's not in front of the PC so the shell should continue the execution after waiting for some time.
Thanks!
From man ksh:
TMOUT
If set to a value greater than zero, the shell terminates if a command is not entered within the prescribed number of seconds after issuing the PS1 prompt. The shell can be compiled with a maximum bound for this value which cannot be exceeded.
I'm not sure whether this works with read in ksh on Solaris. It does work with ksh93, but that version also has read -t.
This script includes this approach:
# Start the (potentially blocking) read process in the background
(read -p && print "$REPLY" > "$Tmp") & readpid=$!
# Now start a "watchdog" process that will kill the reader after
# some time:
(
sleep 2; kill $readpid >/dev/null 2>&1 ||
{ sleep 1; kill -1 $readpid >/dev/null 2>&1; } ||
{ sleep 1; kill -9 $readpid; }
) & watchdogpid=$!
# Now wait for the reading process to terminate. It will terminate
# reliably, either because the read terminated, or because the
# "watchdog" process made it terminate.
wait $readpid
# Now stop the watchdog:
kill -9 $watchdogpid >/dev/null 2>&1
REPLY=TERMINATED # Assume the worst
[[ -s $Tmp ]] && read < "$Tmp"
Look at this forum thread it has the answer in the third post.

Resources