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.
Related
When using tmux I use the wait-for feature where the tmux session is getting notified after the finishing of a command. Sometimes however I want to terminate the command sequence without terminating the wait-for part so the original script doesn't hang.
Basically, if I have:
command 1; command 2
hitting Ctrl-C exits both where I mainly want to exit command 1 but continue executing command 2 (which is the wait-for acknowledgment so the script doesn't hang).
Is there a way to do that?
I already tried:
command 1 || command 2
But Ctrl-C still exits both.
You can try running command 1 in the background and trap the signal that Ctrl + C sends.
#! /bin/bash
(command 1) & # Runs c1 in the background
pid=$! # Stores the PID of the subshell
trap "kill -INT $pid" SIGINT # Makes Ctrl+C kill the background process
wait # Waits for completion of c1
trap - SIGINT # Restores the default behaviour of Ctrl+C
command 2 # Runs the second command
To let the command exit but the script continue on Ctrl-C, just set a no-op sigint trap:
trap "true" INT
sleep 30
echo "Continuing"
If you want to restore the behavior of killing the script, you can use trap - INT.
The following should ensure that if you press ctrl-C, command1, plus any child processes that it may have, get the SIGINT.
#!/bin/bash
# Use "set -m" to test if "-m" option is currently set
# If set, this will ensure that any subprocesses are started
# as process group leaders (we'll need this later)
if [ -z "${-//[^m]/}" ] # -m option not already set
then
set -m
setm=1
else
setm=0
fi
# launch the command and capture its pid
command1 &
pid=$!
# install a trap so that if SIGINT is received, then every
# process in the process group of which command1 is leader
# is sent a SIGINT (note the "-" before $pid)
trap "kill -INT -$pid" SIGINT
# wait for command1 to finish (ignoring any other previously launched
# children that finish meanwhile)
wait $pid
# undo "set -m" setting as appropriate
if [ $setm -eq 1 ]
then
set +m
fi
# cancel the trap
trap - SIGINT
# and carry on
command2
For example, if command1 is itself a shell script, then the command that is being run by that shell script should be properly terminated.
A slight side-effect of using the -m option is that if you do press ctrl-C, then you will receive a message such as:
[1]+ Interrupt command1
This may be deferred until after the next command completes. You could insert a short sleep before command2 (e.g. sleep 0.1) so that any such notification is delivered at the end of the sleep (before command2 runs) rather than after command2.
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.
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"
(Question revised, now that I understand more about what's actually happening):
I have a script that runs in the background, periodically doing some work and then sleeping for 30 seconds:
echo "background script PID: $$"
trap 'echo "Exiting..."' INT EXIT
while true; do
# check for stuff to do, do it
sleep 30
done &
If I try to kill this script via kill or kill INT, it takes 30 seconds to respond to the signal.
I will answer this question below, since I found a good explanation online.
(My original, embarrassingly un-researched question)
This question is for a bash script that includes the following trap:
trap 'echo "Exiting...">&2; kill $childPID 2>/dev/null; exit 0' \
SIGALRM SIGHUP SIGINT SIGKILL SIGPIPE SIGPROF SIGTERM \
SIGUSR1 SIGUSR2 SIGVTALRM SIGSTKFLT
If I run the script in the foreground, and hit
CTRL-C, it gets the signal immediately and exits
(under one sec).
If I run the same script in the background (&), and kill it via
kill or kill -INT, it takes 30 seconds before getting the signal.
Why is that, and how can I fix it?
As explained in http://mywiki.wooledge.org/SignalTrap --
"When bash is executing an external command in the foreground, it does not handle any signals received until the foreground process terminates" - and since sleep is an external command, bash does not even see the signal until sleep finishes.
That page has a very good overview of signal processing in bash, and work-arounds to this issue. Briefly, one correct way of handling the situation is to send the signal to the process group instead of just the parent process:
kill -INT -123 # will kill the process group with the ID 123
Head over to the referenced page for a full explanation (no sense in my reproducing any more of it here).
Possible reason: signals issued while a process is sleeping are not delivered until wake-up of the process. When started via the command line, the process doesn't sleep, so the signal gets delivered immediately.
#RashaMatt, I was unable to get the read command to work as advertised on Greg's wiki. Sending a signal to the script simply did not interrupt the read. I needed to do this:
#!/bin/bash
bail() {
echo "exiting"
kill $readpid
rm -rf $TMPDIR
exit 0
}
sig2() {
echo "doing stuff"
}
echo Shell $$ started.
trap sig2 SIGUSR2
trap bail SIGUSR1 SIGHUP SIGINT SIGQUIT SIGTERM
trap -p
TMPDIR=$(mktemp -p /tmp -d .daemonXXXXXXX)
chmod 700 $TMPDIR
mkfifo $TMPDIR/fifo
chmod 400 $TMPDIR/fifo
while : ; do
read < $TMPDIR/fifo & readpid=$!
wait $readpid
done
...send the desired signal to the shell's pid displayed from the Shell $$ started line, and watch the excitement.
waiting on a sleep is simpler, true, but some os' don't have sleep infinity, and I wanted to see how Greg's read example would work (which it didn't).
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