I have a basic bash script that prints out its pid, then counts to 100:
echo $$
for i in {1..100}
do
echo $i
sleep 1
done
When I press ctrl+c, while this is running, the program will halt with an exit code of 130. I am trying to send a signal that will simulate a ctrl+c and cause the program to halt and exit with code 130.
When I type "kill -INT " from a different terminal window, nothing happens, though from what I understand, it should be the same as hitting ctrl+c. However, when I type "kill -9 ", I can successfully terminate the program, albeit with a different exit code. How do I stop my program with an interrupt signal, so it exits with the proper code?
Prepend process id with dash sign "-"
kill -SIGINT -<pid>
This will kill the process with exit code 130.
UPD: Why not to use SIGTERM(15) which is send by kill command by default (with no signal number or name)?
Related
I'm trying to write a program using bash script. I'd like to give an alert when this program is killed.
The desired action is like this:
#!/bin/bash
... # The original program
if killed ; do
echo "trying to kill the demo program ... "
sleep 5s
echo "demo program killed"
fi
If you expect the signal to be delivered only to the running program and not to the shell running your script, then the basic synopsis might be:
#!/bin/bash
set -euo pipefail
sleep 1 & # The original program
pid="$!"
kill -9 "$pid" # Pick your lethal signal
wait -n "$pid" && status=0 || status="$?"
((status > 128)) && echo "${pid} got signal $((status - 128))" 1>&2 || :
Presumably, here^^^ we run the program in the background, so that we can send it the kill signal from the same snippet. In practice you would probably run it in the foreground and then check its $? return status instead of the status from wait -n.
If the killing signal is delivered to your entire process group, including the shell running your script, that is a different story. For the signal KILL (9) in particular, there is no way to mask it or report it. When the shell gets it, it dies. For other signals you could set up a trap command (see man bash for its syntax) to handle the signal gracefully in the script while still being able to detect and report the child process’ death from the signal.
If I send a signal using kill, it seems to wait until the current program (in this example sleep 1000) finishes running. When I instead send SIGINT via pressing Ctrl+C in the shell, it receives the interrupt immediately however.
What I want, however, is for the interrupt to be received immediately after sending the signal via kill. Also, why does it behave like I would want it to when I press Ctrl+C?
#!/usr/bin/env sh
int_after_a_while() {
local pid=$1
sleep 2
echo "Attempting to kill $pid with SIGINT"
# Here I want to kill the process immediately, but it waits until sleep finishes
kill -s INT $pid
}
trap "echo Interrupt received!" INT
int_after_a_while $$ &
sleep 1000
I would appreciate any help on this issue. Thanks in advance!
As noted in the referenced answer https://unix.stackexchange.com/questions/282525/why-did-my-trap-not-trigger/282631#282631 the shell will normally wait for a utility to complete before running a trap. Some alternatives are:
Start the long running process in the background, then wait for it using the wait builtin. When a trapped signal is received during such a wait, the wait is interrupted and the trap is taken. Unfortunately, the exit status of wait does not distinguish between the child process exiting on a signal and a trap occurring. For example
sleep 1000 &
p=$!
wait "$p"
Send a signal to the whole process group via kill -s INT 0. The effect is much like if the user had pressed Ctrl+C, but may be more extreme than you want if your script is run from another script.
Use a shell such as zsh or FreeBSD sh that supports set -o trapsasync which allows running traps while waiting for a foreground job.
I am trying to write a bash script in which I require to run a binary and if I press Ctrl+c then along with script, binary which is running in background should also be stopped. I am trying this code but it does not help:
#!/bin/bash
cd /usr/local/path-to-binary
./testBinary
read input
if (( "$input" == "Ctrl+c")); then
ps -aef|grep Binary
pkill -9 Binary
There a several errors in your script. Check the comments below your question.
Here is an example how to launch a background process and kill it on Ctrl+c:
#!/bin/bash
# Register signal handler for SIGINT (Ctrl+c)
trap abort INT
function abort() {
echo "Sending SIGINT to background process ${pid}"
# Kill background process
kill "${pid}"
# Wait for it to finish after killing it
wait "${pid}"
# Exit the script
echo "Aborting"
exit 1
}
# Start long running process in background (&)
sleep 1000 &
# Obtain the pid of that process
pid=$!
# Wait for the background process to finish
wait
PS: Just run the script and press Ctrl+c. read is not required.
trap should be executed after the currently running command is finished, but in the following example upon pressing Ctrl+C it interrupts the current command (sleep) before the 10 seconds are over , prints the message "SIGINT received" and then immediately starts the next sleep:
#!/bin/bash
trap 'echo SIGINT received' INT
counter=0
while true
do
let counter=counter+1
echo start sleeping period $counter
sleep 10
done
Is there anything wrong with my use of trap? How can I achieve a behaviour that lets the current command finish after pressing Ctrl+C, without using a child process or subshell (this way is shown here)?
Ctrl+C interrupts the current foreground group, which in your case is both the script itself and sleep.
Since sleep dies on SIGINT, it exits immediately. This causes the message to be printed immediately.
If you want to be able to send a SIGINT to your process without it dying, you can ignore the signal before starting the process and hope that the process doesn't reset the signal action (most programs don't):
#!/bin/bash
trap 'echo SIGINT received' INT
counter=0
while true
do
let counter=counter+1
echo "start sleeping period $counter"
( trap '' INT; exec sleep 10; )
done
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.