I'm using Ruby 1.9.2-p180 on Ubuntu 12.04. I don't understand why the following code doesn't work. The expected behaviour is that I should see the text: "TRAPPED" on my screen when I run the code, and the program should terminate. But the ruby program doesn't terminate when I run it and nothing is printed on the screen. What am I missing?
pid = fork do
exec "trap 'echo TRAPPED' TERM; while :; do :; done"
end
Process.kill("TERM", pid)
Process.wait(pid)
The reason why it did not work as expected was that the kill was delivered before the exec commenced. Introducing a little sleep before delivering the kill ensures that the program will behave as expected.
Related
Say I have a bash script like this:
#!/bin/bash
exec-program zero
exec-program one
the script issued a run command to exec-program with the arg "zero", right? say, for instance, the first line is currently running. I know that Ctrl-C will halt the process and discontinue executing the remainder of the script.
Instead, is there a keypress that will allow the current-line to finish executing and then discontinue the script execution (not execute "exec-program one") (without modifying the script directly)? In this example it would continue running "exec-program zero" but after would return to the shell rather than immediately halting "exec-program zero"
TL;DR Something runtime similar to "Ctrl-C" but more lazy/graceful ??
In the man page, under SIGNALS section it reads:
If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.
This is exactly what you're asking for. You need to set an exit trap for SIGINT, then run exec-program in a subshell where SIGINT is ignored; so that it'll inherit the SIG_IGN handler and Ctrl+C won't kill it. Below is an implementation of this concept.
#!/bin/bash -
trap exit INT
foo() (
trap '' INT
exec "$#"
)
foo sleep 5
echo alive
If you hit Ctrl+C while sleep 5 is running, bash will wait for it to complete and then exit; you will not see alive on the terminal.
exec is for avoiding another fork() btw.
I have a bash script, its contents are:
function foo {
echo "Foo!"
}
function clean {
echo "exiting"
}
trap clean EXIT
trap foo SIGTERM
echo "Starting process with PID: $$"
while :
do
sleep 60
done
I execute this on a terminal with:
./my_script
And then do this on another terminal
kill -SIGTERM my_script_pid # obviously the PID is the one echoed from my_script
I would expect to see the message "Foo!" from the other terminal, but It's not working. SIGKILL works and the EXIT code is also executed.
Using Ctrl-C on the terminal my_script is running on triggers foo normally, but somehow I can't send the signal SIGTERM from another terminal to this one.
Replacing SIGTERM with any other signal doesn't change a thing (besides Ctrl-C not triggering anything, it was actually mapped to SIGUSR1 in the beginning).
It may be worth mentioning that just the signal being trapped is not working, and any other signal is having the default behaviour.
So, what am I missing? Any clues?
EDIT: I also just checked it wasn't a privilege issue (that would be weird as I'm able to send SIGKILL anyway), but it doesn't seem to be that.
Bash runs the trap only after sleep returns.
To understand why, think in C / Unix internals: While the signal is dispatched instantly to bash, the corresponding signal handler that bash has setup only does something like received_sigterm = true.
Only when sleep returns, and the wait system call which bash issued after starting the sleep process returns also, bash resumes its normal work and executes your trap (after noticing received_sigterm).
This is done this way for good reasons: Doing I/O (or generally calling into the kernel) directly from a signal handler generally results in undefined behaviour as far as I know - although I can't tell more about that.
Apart from this technical reason, there is another reason why bash doesn't run the trap instantly: This would actually undermine the fundamental semantics of the shell. Jobs (this includes pipelines) are executed strictly in a sequential manner unless you explicitly mess with background jobs.
The PID that you originally print is for the bash instance that executes your script, not for the sleep process that it is waiting on. During sleep, the signal is likely to be ignored.
If you want to see the effect that you are looking for, replace sleep with a shorter-lived process like ps.
function foo {
echo "Foo!"
}
function clean {
echo "exiting"
}
trap clean EXIT
trap foo SIGTERM
echo "Starting process with PID: $$"
while :
do
ps > /dev/null
done
I am executing a shell script in background from my tcl script. The tcl script ends execution after some time. At this point I assume the background shell script becomes orphan and is adopted by init.
set res [catch { exec sudo $script &}]
Now the problem is I am not able to signal my (orphaned) background script. But why? Ok it now belongs to init but why can't I signal it. Only sigkill seems to work and that kills it - I need to trigger the signal handler I've written to handle SIGUSR2
trap 'process' SIGUSR2
Why can't I signal my orphan background process? Is there no way this can be done? Or is there some workaround?
EDIT: Seems to work fine when the sleep is not involved. See sample code below:
trap 'kill `cat /var/run/sleep.pid`; foo' SIGUSR2;
foo(){ echo test; }
while true; do
echo -n .
sleep 100 &
echo ${!} > /var/run/sleep.pid
wait ${!}
done
Works fine when not orphaned - but in the case of orphan process I think the problem is the true pid of sleep gets overwritten and I'm not able to kill it when the trap arrives.
lets run a small script like that:
bash -c '(trap foo SIGUSR2;foo(){ echo test; };while true; do echo -n .;sleep 1;done) & echo $!'; read
It will fork a background process which just runs and outputs some dots. It will also output the PID of the process, which you can use to check and signal it.
$ ps -f 19489
UID PID PPID C STIME TTY STAT TIME CMD
michas 19489 1 0 23:45 pts/8 S 0:00 bash -c (trap foo SIGUS...
Because the forking shell died directly after running the command in background, the process is now owned by init (PPID=1).
Now you can signal the process to call the handler:
kill -USR2 19489
If you do, you will notice the "test" output at the terminal printing the dots.
There should be no difference, whether you start a background process from shell or tcl. If it runs you can send it a signal and if there is a handler, it will be called.
If it really does not answer to signals it might be blocked, waiting for something. For example in a sleep or waiting for some IO.
My sample file
traptest.sh:
#!/bin/bash
trap 'echo trapped' TERM
while :
do
sleep 1000
done
$ traptest.sh &
[1] 4280
$ kill %1 <-- kill by job number works
Terminated
trapped
$ traptest.sh &
[1] 4280
$ kill 4280 <-- kill by process id doesn't work?
(sound of crickets, process isn't killed)
If I remove the trap statement completely, kill process-id works again?
Running some RHEL 2.6.18-194.11.4.el5 at work. I am really confused by this behaviour, is it right?
kill [pid]
send the TERM signal exclusively to the specified PID.
kill %1
send the TERM signal to the job #1's entire process group, in this case to the script pid + his children (sleep).
I've verified that with strace on sleep process and on script process
Anyway, someone got a similar problem here (but with SIGINT instead of SIGTERM): http://www.vidarholen.net/contents/blog/?p=34.
Quoting the most important sentence:
kill -INT %1 sends the signal to the job’s process group, not the backgrounded pid!
This is expected behavior. Default signal sent by kill is SIGTERM, which you are catching by your trap. Consider this:
#!/bin/bash
# traptest.sh
trap "echo Booh!" SIGINT SIGTERM
echo "pid is $$"
while : # This is the same as "while true".
do
a=1
done
(sleep really creates a new process and the behavior is clearer with my example I guess).
So if you run traptest.sh in one terminal and kill TRAPTEST_PROCESS_ID from another terminal, output in the terminal running traptest will be Booh! as expected (and the process will NOT be killed). If you try sending kill -s HUP TRAPTEST_PROCESS_ID, it will kill the traptest process.
This should clear up the %1 confusion.
Note: the code example is taken from tldp
Davide Berra explained the difference between kill %<jobspec> and kill <PID>, but not how that difference results in what you observed. After all, Unix signal handlers should be called pretty much instantaneously, so why does sending a SIGTERM to the script alone not trigger its trap handler?
The bash man page explains why, in the last paragraph of the SIGNALS section:
If bash is waiting for a command to complete and receives a signal for
which a trap has been set, the trap will not be executed until the
command completes.
So, the signal was delivered immediately, but the handler execution was deferred until sleep exited.
Hence, with kill %<jobspec>:
Both the script and sleep received SIGTERM
bash registered the signal, noticed that a trap was set for it, and queued the handler for future execution
sleep exited immediately
bash noted sleep's exit, and ran the trap handler
whereas with kill <script_PID>:
Only the script received SIGTERM
bash registered the signal, noticed that a trap was set for it, and queued the handler for future execution
sleep exited after 1000 seconds
bash noted sleep's exit, and ran the trap handler
Obviously, you didn't want long enough to see that last bit. :)
If you're interested in the gory details, download the bash source code and look in trap.c, specifically the trap_handler() and run_pending_traps() functions.
I want to write a shell script that spawns several long-running processes in the background, then hangs around. Upon receiving SIGTERM, I want all the subprocesses to terminate as well.
Basically, I want a "master process".
Here's what I got so far:
#!/bin/sh
sleep 600 &
PID1="$!"
sleep 600 &
PID2="$!"
# supposedly this should kill the child processes on SIGTERM.
trap "kill $PID1 $PID2" SIGTERM
wait
The above script fails with trap: 10: SIGTERM: bad trap.
Edit: I'm using Ubuntu 9.04
This works for me:
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
kill -- -$$ sends a SIGTERM to the whole process group, thus killing also descendants.
Specifying signal EXIT is useful when using set -e (more details here).
Joe's answer put me on the right track.
I also found out I should trap more signals to cover my bases.
Final script looks like this:
#!/bin/sh
sleep 600 &
PID1="$!"
sleep 600 &
PID2="$!"
trap "kill $PID1 $PID2" exit INT TERM
wait
I suspect your /bin/sh is not a Bash (though you tagged the question as 'Bash').
From the message I guess it's a DASH. Check its manual or just fix your shebang if you need to write Bash code.
This script looks correct and works for me as expected.
How do you send the SIGTERM signal to the "master process"?
Maybe you should execute kill -l to check which signals are supported.
As the error message suggests you send signal "10" which your system doesn't seem to recognize.
And next time you should add operating system, shell version, kernel, ... for such a question