TERM trap doesn't reliably kill backgrounded sleep process - bash

I wrote some BASHscript that shows a spinner and noticed that sometimes after the background process would finish and I killed the spinner, the sleep process was still running and delaying processing. I reduced the code to the one below:
#!/bin/bash
trap 'rm deleteme.fifo' EXIT
mkfifo deleteme.fifo
echo we are $$
bar() {
trap 'echo killing $sleep_pid from $$; kill $sleep_pid; wait $sleep_pid; echo exit subshell; exit' TERM
sleep 20 &
sleep_pid=$!
echo sleep is $sleep_pid
echo >deleteme.fifo
echo before wait
wait $sleep_pid
echo after wait
}
foo() {
bar &
bar_pid=$!
read <deleteme.fifo
kill $bar_pid && echo waiting for subshell to terminate && wait $bar_pid; echo returning
}
while true; do
foo &
wait $!
done
In bar I set up the trap and start a 20s sleep then notify the parent process that we are ready through a named pipe. Then I kill the child from foo. This often works, but sometimes the sleep survives and ticks down with the rest waiting for it. That's why I added the loop to trigger the behavior eventually. Does anyone have an explanation for this?
bash 4.4.12(1), Linux 4.14.83, coreutils 8.29

Related

Prevent CTRL+C being sent to process called from a Bash script

Here is a simplified version of some code I am working on:
#!/bin/bash
term() {
echo ctrl c pressed!
# perform cleanup - don't exit immediately
}
trap term SIGINT
sleep 100 &
wait $!
As you can see, I would like to trap CTRL+C / SIGINT and handle these with a custom function to perform some cleanup operation, rather than exiting immediately.
However, upon pressing CTRL+C, what actually seems to happen is that, while I see ctrl c pressed! is echoed as expected, the wait command is also killed which I would not like to happen (part of my cleanup operation kills sleep a bit later but first does some other things). Is there a way I can prevent this, i.e. stop CTRL+C input being sent to the wait command?
You can prevent a process called from a Bash script from receiving sigint by first ignoring the signal with trap:
#!/bin/bash
# Cannot be interrupted
( trap '' INT; exec sleep 10; )
However, only a parent process can wait for its child, so wait is a shell builtin and not a new process. This therefore doesn't apply.
Instead, just restart the wait after it gets interrupted:
#!/bin/bash
n=0
term() {
echo "ctrl c pressed!"
n=$((n+1))
}
trap term INT
sleep 100 &
while
wait "$!"
[ "$?" -eq 130 ] # Sigint results in exit code 128+2
do
if [ "$n" -ge 3 ]
then
echo "Jeez, fine"
exit 1
fi
done
I ended up using a modified version of what #thatotherguy suggested:
#!/bin/bash
term() {
echo ctrl c pressed!
# perform cleanup - don't exit immediately
}
trap term SIGINT
sleep 100 &
pid=$!
while ps -p $pid > /dev/null; do
wait $pid
done
This checks if the process is still running and, if so, runs wait again.

Kill not killing process if exiting properly

I have a simple bash script which I have written to simplify some work I am doing. All it needs to do is start one process, process_1, as a background process then start another, process_2. Once process_2 is finished I then need to terminate process_1.
process_1 starts a program which does not actually stop unless it receives the kill signal, or CTRL+C when I run it myself. The program is output into a file via {program} {args} > output_file
process_2 can take an arbitrary amount of time depending on the arguments it is given.
Code:
#!/bin/bash
#Call this on exit to kill all background processes
function killJobs () {
#Check process is still running before killing
if kill -0 "$PID"; then
kill $PID
fi
}
...Check given arguments are valid...
#Start process_1
eval "./process_1 ${Arg1} ${Arg2} ${Arg3}" &
PID=$!
#Lay a trap to catch any exits from script
trap killJobs TERM INT
#Start process_2 - sleep for 5 seconds before and after
#Need space between process_1 and process_2 starting and stopping
sleep 5
eval "./process_2 ${Arg1} ${Arg2} ${Arg3} ${Arg4} 2> ${output_file}"
sleep 5
#Make sure background job is killed on exit
killJobs
I check process_1 has been terminated by checking of its output file is still being updated after my script has ended.
If I run the script and then press CTRL+C the script is terminated and process_1 is also killed, the output file is no longer updated.
If I let the script run to its completion without my intervention process_2 and the script both terminate but when I check the output from process_1 it is still being updated.
To check this I put an echo statement just after process_1 is started and another within the if statement of killJobs, so it would only be echoed if kill $PID is called.
Doing this I can see that both ways of exiting start process_1 and then also enter the if statement to kill it. Yet kill does not actually kill the process in the case of normal exit. No error messages are produced either.
You're backgrounding the eval instead of process_1, which sets $! to the PID of the script itself, not to process_1. Change to:
#!/bin/bash
#Call this on exit to kill all background processes
function killJobs () {
#Check process is still running before killing
if kill -0 "$PID"; then
kill $PID
fi
}
...Check given arguments are valid...
#Start process_1
./process_1 ${Arg1} ${Arg2} ${Arg3} &
PID=$!
#Lay a trap to catch any exits from script
trap killJobs TERM INT
#Start process_2 - sleep for 5 seconds before and after
#Need space between process_1 and process_2 starting and stopping
sleep 5
./process_2 ${Arg1} ${Arg2} ${Arg3} ${Arg4} 2> ${output_file}
sleep 5
#Make sure background job is killed on exit
killJobs

BASH: Pause and resume a child script

I want to control a child script somehow. I am making a master script which spawns many children scripts and need to RESUME and PAUSE them on demand.
Child
Do stuff
PAUSE
Cleanup
Parent
sleep 10
RESUME child
Is this possible?
AS PER SUGGESTIONS
Trying to do it with signals while the child runs in the background doesn't seem to work.
script1:
#!/bin/bash
"./script2" &
sleep 1
kill -2 "$!"
sleep 1
script2:
#!/bin/bash
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...' SIGINT
trap 'echo you hit ctrl-\, stoppng...; exit' SIGQUIT
while [ 1 ]
do
echo "Waiting for signal.."
sleep 60000
echo "~~EXIT1"
done
echo "~~EXIT2"
Running:
> ./script1
One way to control individual process scripts is with signals. If you combine SIGINT (ctrl-c) to resume with SIGQUIT (ctrl-) to kill then the child process looks like this:
#!/bin/sh
trap 'echo you hit ctrl-c, waking up...' SIGINT
trap 'echo you hit ctrl-\, stoppng...; exit' SIGQUIT
while (true)
do
echo "do the work..."
# pause for a very long time...
sleep 600000
done
If you run this script, and hit ctrl-c, the work continues. If you hit ctrl-\, the script stops.
You would want to run this in the background then send kill -2 $pid to resume and kill -3 $pid to stop (or kill -9 would work) where $pid is the child process's process id.
Here is a good bash signals reference: http://www.ibm.com/developerworks/aix/library/au-usingtraps/
-- here is the parent script...
#!/bin/sh
./child.sh &
pid=$!
echo "child running at $pid"
sleep 2
echo "interrupt the child at $pid"
kill -INT $pid # you could also use SIGCONT
sleep 2
echo "kill the child at $pid"
kill -QUIT $pid
One way is to create a named pipe per child:
mkfifo pipe0
Then redirect stdin of the child to read from the pipe:
child < pipe0
to stop the child:
read _
(the odd _ is just there for read to have a place to store the empty line it will read).
to resume the child:
echo > pipe0
A more simple approach would be to save the stdin which gets passed to the child in form a pure file descriptor but I don't know the exact syntax anymore and can't google a good example ATM.

Signalling a bash script running in the background in an infinite loop

I have a bash script (sleeping in an infinite loop) running as a background process. I want to periodically signal this process, to do some processing without killing the script, ie. the script on receiving the signal should run a function and then go back to sleep. How do I signal the background process without killing it?
Here's what the code looks like for the script test.sh:
MY_PID=$$
echo $MY_PID > test.pid
while true
do
sleep 5
done
trap 'run' SIGUSR1
run()
{
// data processing
}
This is how I am running it and triggering it:
run.sh &
kill -SIGUSR1 `cat test.pid`
This works for me.
EDITED 2014-01-20: This edit avoids the frequent waking up. Also see this question:
Bash: a sleep in a while loop gets its own pid
#!/bin/bash
MY_PID=$$
echo $MY_PID > test.pid
trap 'kill ${!}; run' SIGUSR1
run()
{
echo "signal!"
}
while true
do
sleep 1000 & wait ${!}
done
Running:
> ./test.sh &
[1] 14094
> kill -SIGUSR1 `cat test.pid`
> signal!
>

How to capture Ctrl-C and use it to exit an endless loop properly

I'm trying to run a program inside an endless loop because it sometimes dies for no reason. I would like to be able to hit Ctrl-C to prevent the program being restarted though.
I don't want Ctrl-C to kill the program, just to wait until it dies, then not restart it again.
theprogram is a wine program (utorrent).
Bonus points for telling me how to make it so it will safely exit theprogram just like clicking on the 'x' in the top right of it. When I manually kill it from the command line or hit Ctrl-C, it doesn't get to run its cleanup code. Hence my attempt to just stop it being restarted.
I checked a few of the other questions about trapping SIGINT, but I couldn't work out how to do this.
Can anyone fix this code? My code seems to kill theprogram then exit the loop when Ctrl-C is pressed, without letting theprogram clean up.
#!/bin/bash
EXIT=0
trap exiting SIGINT
exiting() { echo "Ctrl-C trapped, will not restart utorrent" ; EXIT=1;}
while [ $EXIT -eq 0 ] ; do
wine theprogram
echo "theprogram killed or finished"
date
echo "exit code $?"
echo "sleeping for 20 seconds, then restarting theprogram..."
sleep 20
done
echo "out of loop"
Try this:
while true
do
xterm -e wine theprogram || break
sleep 3
done
The trick is done by using another xterm to start the wine. That way the wine has a different controlling tty and won't be affected by the Ctrl-c press.
The drawback is that there will be an additional xterm lingering around on your desktop. You could use the option -iconic to start it iconified.
Well, I ended up not using Ctrl-C as per my question because I couldn't find a good solution, but I used zenity to popup a box that I can click to exit the loop:
#!/bin/bash
zenity --info --title "thewineprogram" --text "Hit OK to disable thewineprogram auto-restart" & # run zenity in the background
zen_pid=$!
while :
do
wine <wineprogramlocation>
EXITCODE=$?
echo "thewineprog killed or finished"
echo "exit code was $EXITCODE"
date
kill -0 $zen_pid > /dev/null 2>&1 # kill -0 just checks if a pid exists
if [ $? -eq 1 ] # process does not exist
then
break
fi
echo "sleeping for 5 seconds, then restarting the wine program..."
sleep 5
done
echo "finished"
Use a monitoring process:
This allows the SIGINT signal to hit the monitor process trap handler without affecting the child.
(this could also be done in perl, python or any language)
#!/bin/bash
cmd() {
trap '' INT
trap 'echo "Signal USR1 received (pid=$BASHPID)"; EXIT=1' USR1
EXIT=0
while [ $EXIT -eq 0 ]
do
echo "Starting (pid=$BASHPID)..."
sleep 5 # represents "wine theprogram"
echo "theprogram killed or finished"
date
echo "Exit code $?"
if [ $EXIT -eq 0 ]; then
echo "Sleeping for 2 seconds, then restarting theprogram..."
sleep 2
fi
done
echo "Exiting (pid=$BASHPID)"
}
run() { cmd & PID=$!; echo Started $PID; }
graceful_exit() { kill -s USR1 $PID && echo "$PID signalled to exit (USR1)"; }
shutdown() { kill -0 $PID 2>/dev/null && echo "Unexpected exit, killing $PID" && kill $PID; }
trap 'graceful_exit' INT
trap 'shutdown' EXIT
run
while :
do
wait && break
done
echo "Exiting monitor process"
It appears that trap on SIGINT must terminate the currently executing sub-command. The only exception appears to be the empty-string handler.
To demonstrate this: When ctrl-c is pressed this (trap "" INT;echo 1;sleep 5;echo 2) does not halt the sleep command. However this (trap "echo hi" INT;echo 1;sleep 5;echo 2) does. After this trap handler executes, execution continues on the command that follows, specifically "echo 2". So empty-string as a handler seems to be a special case which does not kill the current sub-command. There seems to be no way to run a handler plus not kill the current sub-command.
Why this happens: Shell forks + execs to execute each program. On system call exec, it resets signal handlers to their default behavior (calling process is overwritten so the handlers are gone). Ignored signals are inherited (see "man 2 execve", "man 7 signal" and POSIX.1; http://www.perlmonks.org/?node_id=1198044)
I had a second idea: use 'trap "" INT' to fully disable ctrl-c and then trap ctrl-z as the signal to gracefully exit your program. Only trapping ctrl-z (STP) seems to not work properly for me. When I run '(trap "echo test" TSTP;sleep 5)' and press ctrl-z, my shell is hung. sleep never completes after 5 seconds and oddly ctrl-c no longer works. I don't know any other hotkey-signals to use other than ctrl-c and ctrl-z. This is known behavior: see Bash script: can not properly handle SIGTSTP.

Resources