cannot trap SIGINT signal for bash shell - bash

I have a script that calls
#!/usr/bin/env bash
my_trap(){
echo "signal was trapped";
}
trap my_trap INT
trap my_trap SIGINT
echo "here is the pid: $$"
echo "here is the parent id just in case: $(ps -o ppid= -p $$)"
tsc --watch # open a server / watch process
I call that script in terminal window 1. In another terminal window (terminal 2) I call
kill -INT <pid>
I call that for both the pid and parent pid echoed in the other terminal
nothing gets trapped - nothing gets logged ("signal was trapped" does not get logged)..
anyone know why SIGINT cannot be caught? Note that if I use ctrl-c in terminal 1, it does get caught, so something about sending SIGINT from another terminal window is not working.

Related

Sending SIGINT to foreground process works but not background

I have two scripts. script1 spawns script2 and then sends a SIGINT signal to it. However the trap in script2 doesn't seem to work?!
script1:
#!/bin/bash
./script2 &
sleep 1
kill -SIGINT $!
sleep 2
script2:
#!/bin/bash
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...' SIGINT
sleep infinity
echo "~~EXIT"
If change ./script2 & to ./script2 and press CTRL+C the whole things works fine. So what am I doing wrong?
You have several issues in your examples, at the end I have a solution for your issue:
your first script seems to miss a wait statement, thus, it exits
after roughly 3 seconds. However script2 will remain in memory and
running.
How do you want bash to automatically figure which process it should
send the SIGINT signal ?
Actually bash will disable SIGINT (and SIGQUIT) on background processes and they can't be enabled (you can check by running trap command alone to check the current status of set traps). See How to send a signal SIGINT from script to script ? BASH
So your script2 is NOT setting a trap on SIGINT because it's a background process, both SIGINT and SIGQUIT are ignored and can't be anymore trapped nor resetted on background processes.
As a reference, here are the documentation from bash related to your issue:
Process group id effect on background process (in Job Control section of doc):
[...] processes whose process group ID is equal to the current terminal
process group ID [..] receive keyboard-generated signals such as
SIGINT. These processes are said to be in the foreground.
Background processes are those whose process group ID differs from
the terminal's; such processes are immune to keyboard-generated
signals.
Default handler for SIGINT and SIGQUIT (in Signals section of doc):
Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent. When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.
and about modification of traps (in trap builtin doc):
Signals ignored upon entry to the shell cannot be trapped or reset.
SOLUTION 1
modify your script1 to be:
#!/bin/bash
{ ./script2; } &
sleep 1
subshell_pid=$!
pid=$(ps -ax -o ppid,pid --no-headers | sed -r 's/^ +//g;s/ +/ /g' |
grep "^$subshell_pid " | cut -f 2 -d " ")
kill -SIGINT $pid
sleep 2
wait ## Don't forget this.
How does this work ? Actually, the usage of { and } will create a subshell, that will be limited by the explained limitation on SIGINT, because this subshell is a background process. However, the subshell's own subprocess are foreground and NOT background processes (for our subshell scope)... as a consequence, they can trap or reset SIGINT and SIGQUIT signals.
The trick is then to find the pid of this subprocess in the subshell, here I use ps to find the only process having the subshell's pid as parent pid.
SOLUTION 2
Actually, only direct new process managed as job will get their SIGINT and SIGQUIT ignored. A simple bash function won't. So if script2 code was in a function sourced in script1, here would be your new script1 that doesn't need anything else:
#!/bin/bash
script2() {
## script2 code
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...' SIGINT
sleep infinity
echo "~~EXIT"
}
## script1 code
script2 &
sleep 1
kill -SIGINT $!
sleep 2
This will work also. Behind the scene, the same mecanism than SOLUTION 1 is working: a bash function is very close to the { } construct.
I guess what you are trying to achieve is that when script2 receives the SIGINT it continues and prints the message. Then, you need
#!/bin/bash
echo "~~ENTRY"
trap 'echo you hit ctrl-c, waking up...; CONT=true' SIGINT
CONT=false
while ! $CONT
do
sleep 1
done
echo "~~EXIT"

Redirect stdin to stdout while waiting for background process

I have the following code:
cmd cmd_args & WAIT_PID=$!
trap "kill -s SIGTERM $WAIT_PID" SIGTERM SIGINT SIGKILL
wait $WAIT_PID
With this I want to be able to kill any background process whenever someone tries to kill this script. However, I still want stdin to be redirected to the background process, which is not happening.
I would like to grab wait's stdin and redirect it to the background process. For that, I've tried:
wait $WAIT_PID 0>&1 1> named_pipe
But without any luck.
As mentioned, you can't trap SIGKILL (or SIGSTOP).
Other than that, try this:
#!/bin/bash
cmd cmd_args <&0 &
wait_pid=$!
trap "kill -s SIGTERM $wait_pid" SIGTERM SIGINT
wait $wait_pid
The <&0 will tell bash that you want cmd's stdin to be the script's stdin.
Slightly off topic, but another interesting method of dumping bytes into a running process' stdin is to send them directly to it's /proc file descriptor, as in:
echo "Stuff to send to the process" >/proc/$wait_pid/fd/0

Sending kill -s USR1 from bash script stops the process while doing the same from terminal does not

There's a nodejs script called mimosa (https://github.com/dbashford/mimosa)
Nodejs uses USR1 to switch the running process to debug mode
Here's how I do it manually
$ cd myproj
$ mimosa watch -s # this runs node /path/to/mimosa watch -s
22:16:03 - Watching /Users/admin/Work/test-mimosa/assets
... # some more output
# check the pid from a different terminal
$ ps aux | grep mimosa
admin 79284 0.7 0.8 3153812 129272 s006 S+ 10:16PM 0:03.57 node /opt/local/bin/mimosa watch -s
# send debug signal from the 2nd terminal
kill -s USR1 79284
# nodejs output in the 1st terminal
Hit SIGUSR1 - starting debugger agent.
debugger listening on port 5858
The same works if I run mimosa as a background process (mimosa watch -s &)
Now I need to automate the process: run mimosa, get its pid, send USR1, wait for user's SIGTERM, kill mimosa:
mimosa watch -s &
pid=$!
echo "mimosa pid: $pid"
trap "echo '\nSTOP'; kill $pid; exit" SIGHUP SIGINT SIGTERM
echo 'send debug'
kill -s USR1 $pid
wait $pid
This script exits immediately, so does the mimosa process (I check it with grep again).
The output in the console
$ ./debug.sh
mimosa pid: 79516
send debug
./debug.sh: line 11: 79516 User defined signal 1: 30 mimosa watch -s
What's wrong, how to fix?
Could mimosa be sending a signal to its own process group when you send the debug signal? That would explain it.
In interactive shells, doing ./program starts program with its own process group. If program does something like kill -s USR1 0, it'll never exit that group.
In non-interactive shells / scripts, doing ./program will start it as a child but in the same process group. If the child does kill -s USR1 0, it'll kill the calling script.
You could do trap 'echo ignoring' USR1 USR2 in your debug.sh in case those are the signals being sent by mimosa.
Alternatively, try turning on job control with set -m before starting mimosa.
See also I have "trap 'echo ignore' USR1" in my called script, why does the calling script get killed?

Set trap in bash for different process with PID known

I need to set a trap for a bash process I'm starting in the background. The background process may run very long and has its PID saved in a specific file.
Now I need to set a trap for that process, so if it terminates, the PID file will be deleted.
Is there a way I can do that?
EDIT #1
It looks like I was not precise enough with my description of the problem. I have full control over all the code, but the long running background process I have is this:
cat /dev/random >> myfile&
When I now add the trap at the beginning of the script this statement is in, $$ will be the PID of that bigger script not of this small background process I am starting here.
So how can I set traps for that background process specifically?
(./jobsworthy& echo $! > $pidfile; wait; rm -f $pidfile)&
disown
Add this to the beginning of your Bash script.
#!/bin/bash
trap 'rm "$pidfile"; exit' EXIT SIGQUIT SIGINT SIGSTOP SIGTERM ERR
pidfile=$(tempfile -p foo -s $$)
echo $$ > "$pidfile"
# from here, do your long running process
You can run your long running background process in an explicit subshell, as already shown by Petesh's answer, and set a trap inside this specific subshell to handle the exiting of your long running background process. The parent shell remains unaffected by this subshell trap.
(
trap '
trap - EXIT ERR
kill -0 ${!} 1>/dev/null 2>&1 && kill ${!}
rm -f pidfile.pid
exit
' EXIT QUIT INT STOP TERM ERR
# simulate background process
sleep 15 &
echo ${!} > pidfile.pid
wait
) &
disown
# remove background process by hand
# kill -TERM ${!}
You do not need trap to just run some command after a background process terminates, you can instead run through a shell command line and add the command following after the background process, separated with semicolon (and let this shell run in the background instead of the background process).
If you still would like to have some notification in your shell script send and trap SIGUSR2 for instance:
#!/bin/sh
BACKGROUND_PROCESS=xterm # for my testing, replace with what you have
sh -c "$BACKGROUND_PROCESS; rm -f the_pid_file; kill -USR2 $$" &
trap "echo $BACKGROUND_PROCESS ended" USR2
while sleep 1
do
echo -n .
done

bash trap will echo from keyboard Ctrl-C while not kill 2

Say I have a script:
#!/bin/bash
# test_trap.sh
trap "echo SIGINT captured!" SIGINT
echo $$
sleep 1000
I know trap COMMAND will only be executed after sleep 1000 finishes when it receives SIGINT signal. But the command of trap will be executed when I pressed keyboard Ctrl-C:
> sh test_sh.sh
50138
^CSIGINT captured!
And using kill -s SIGINT will not.
What am I missing here?
The bash version is GNU bash, 4.2.46(2)-release
With kill -s SIGINT 50138, you are only sending the signal to the shell's process, and that has to wait for sleep 1000 to finish, because sleep doesn't receive the signal.
Control-C, though, causes the terminal to send SIGINT to every process in the current process group, so both your shell script and sleep receive it. Your script still doesn't process the trap command until sleep completes, but sleep exits immediately in response to the SIGINT it just received from the terminal.
If your kill supports it, you can also use kill -s SIGINT -50138 (note the negative process id) to send SIGINT to the entire process group.

Resources