SIGALRM waits for subshell processes? - shell

Here is the unexpected situation: in the following script, SIGALRM doesn't invoke the function alarm() at the expected time.
#!/bin/sh -x
alarm() {
echo "alarmed!!!"
}
trap alarm 14
OUTER=$(exec sh -c 'echo $PPID')
#for arg in `ls $0`; do
ls $0 | while read arg; do
INNER=$(exec sh -c 'echo $PPID')
# child A, the timer
sleep 1 && kill -s 14 $$ &
# child B, some other scripts
sleep 60 &
wait $!
done
Expectation:
After 1 second, the function alarm() should be called.
Actually:
alarm() is called until 60s, or when we hit Ctrl+C.
We know in the script, $$ actually indicates the OUTER process, so I suppose we should see the string printed to screen after 1 second. However, it is until child B exits do we see alarm() is called.
When we get the trap line commented, the whole program just terminates after 1 second. So... I suppose SIGALRM is at least received, but why doesn't it invoke actions?
And, as a side question, is the default behavior of SIGALRM to be termination? From here I am told that by default it is ignored, so why OUTER exits after receiving it?

From the bash man page:
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. When bash is waiting for an asynchronous command via the wait
builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately
with an exit status greater than 128, immediately after which the trap is executed.
Your original script is in the first scenario. The subshell (the while read loop) has called wait, but the top level script is just waiting for the subshell, so when it receives the signal, the trap is not executed until the subshell completes. If you send the signal to the subshell with kill -s 14 $INNER, you get the behavior you expect.

Related

Why can't I exit from an exit trap when I'm inside of a function in ZSH, unless I'm in a loop?

I'm really trying to understand the difference in how ZSH and Bash are handling signal traps, but I'm having a very hard time grasping why ZSH is doing what it's doing.
In short, I'm not able to exit a script with exit in ZSH from within a trap if the execution point is within a function, unless it's also within a loop.
Here is an example of how exit in a trap action behaves in the global / file level scope.
#!/bin/zsh
trap 'echo "Trap SIGINT" ; exit 130' SIGINT
sleep 1
echo "1"
sleep 1
echo "2"
sleep 1
echo "3"
If I call the script, I can send an INT signal by pressing Cntrl+C at any time to echo "Trap SIGINT" and exit the script immediately.
If I hit Cntrl+C after I see the first 1, the output looks like this:
$ ./foobar
1
^CTrap SIGINT
But if I wrap the code in a function, then the trap doesn't want to stop script execution until the function finishes. Using exit 130 from within the trap action just continues the code from the execution point within the function.
Here is an example of how using trap behaves in the function level scope.
#!/bin/zsh
trap 'echo "Trap SIGINT" ; exit 130' SIGINT
foobar() {
sleep 1
echo "1"
sleep 1
echo "2"
sleep 1
echo "3"
}
foobar
echo "Finished"
If I call the script, the only thing that an INT signal does is end the sleep command early. The script will just keep on going from the same execution point after that.
If I hit Cntrl+C repeatedly the output looks like this.
$ ./foobar
^CTrap SIGINT
1
^CTrap SIGINT
2
^CTrap SIGINT
3
It doesn't echo the "Finished" at the end, so it is exiting when the function is finished, but I can't seem to exit before it's finished.
It doesn't make a difference if I set the trap in the global / file scope or from within the function.
If I change exit 130 to return 130, then it will jump out of that function early but continue script execution. This is expected behavior from what I could read in the ZSH documentation.
If I wrap the code inside of a for or while loop as shown in the code below, the code then has no problem breaking out of the loop.
#!/bin/zsh
trap 'echo "Trap SIGINT" ; exit 130' SIGINT
foobar() {
for i in 1; do
sleep 1
echo "1"
sleep 1
echo "2"
sleep 1
echo "3"
done
sleep 1
echo "Outside of loop"
}
foobar
echo "Finished"
Even if I have the loop in the global / file scope and calling foobar from within the loop, it still has no problem exiting within the trap action. I assume it's because using
The one thing that does work correctly is defining a TRAPINT function instead of using the trap built-in, and returning a non-exit code from that function. However exiting from the TRAPINT function works the same way it does with the trap built-in.
I've tried to find anything on why it acts like this but I couldn't find anything.
So what's actually happening here? Why is ZSH not letting me exit from the trap action when the execution point is inside a function?
One way to make this work as expected is setting the ERR_EXIT option.
From the documentation:
If a command has a non-zero exit status, execute the ZERR trap, if set, and exit. This is disabled while running initialization scripts.
There's also ERR_RETURN:
If a command has a non-zero exit status, return immediately from the enclosing function. The logic is similar to that for ERR_EXIT, except that an implicit return statement is executed instead of an exit. This will trigger an exit at the outermost level of a non-interactive script.
Both options have some caveats and notes; refer to the documentation.
Adding a setopt localoptions err_exit as the first line of the foobar function (You probably don't want to do this globally) in your script causes:
$ ./foobar
1
^CTrap SIGINT
$
Now, the interesting bit. In your demonstration script, if you change your exit value from 130 to some other number, and the echo lines to echo "1 - $?" etc., you get:
$ ./foobar
1 - 0
2 - 0
^CTrap SIGINT
3 - 130
The sleep is still exiting with 130, the normal value for a process killed by a SIGINT. What happened to your exit in the trap and its value? Not a clue (I'll update the answer if I figure it out) .
I'd just stick with the TRAPnal functions when writing zsh scripts that care about signals.

Is there a way to terminate only the first command in a sequence of commands?

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.

Why is the KILL signal handler not executing when my child process dies

I have migrated some scripts from ksh to bash and I have observed very strange behavior in bash. I was able to reduce to a very short snippet.
echo first test
LC_ALL=C xclock &
Active_pid=$!
sleep 1
kill -9 $Active_pid
sleep 1
echo second test
LC_ALL=C xclock &
Active_pid=$!
sleep 1
trap "echo Signal SIGKILL caught" 9
kill -9 $Active_pid
sleep 1
The output is
first test
./mig_bash.sh: line 15: 4471 Killed LC_ALL=C xclock
second test
My problem was the production of the trace in the first test. I have tried to see if a signal was received. By trial and error, I wrote the "second test" that solve my problem. I do not understand it. How this removes the trace of the first test without executing echo Signal SIGKILL ?
I am completely lost.
I couldn't find anything in the bash documentation that would explain the observed behavior, so I turned to the source code. Debugging lead to the function notify_of_job_status(). The line that prints the message about a killed subprocess can be reached only if all of the following conditions hold:
the subprocess is registered in the job table (i.e. has not been disown-ed)
the shell was NOT started in interactive mode
the signal that terminated the child process is NOT trapped in the parent shell (see the signal_is_trapped (termsig) == 0 check)
Demonstration:
$ cat test.sh
echo Starting a subprocess
LC_ALL=C sleep 100 &
Active_pid=$!
case "$1" in
disown) disown ;;
trapsigkill) trap "echo Signal SIGKILL caught" 9 ;;
esac
sleep 1
kill -9 $Active_pid
sleep 1
echo End of script
$ # Demonstrate the undesired message
$ bash test.sh
Starting a subprocess
test.sh: line 14: 15269 Killed LC_ALL=C sleep 100
End of script
$ # Suppress the undesired message by disowning the child process
$ bash test.sh disown
Starting a subprocess
End of script
$ # Suppress the undesired message by trapping SIGKILL in the parent shell
$ bash test.sh trapsigkill
Starting a subprocess
End of script
$ # Suppress the undesired message by using an interactive shell
$ bash -i test.sh
Starting a subprocess
End of script
How this removes the trace of the first test without executing echo Signal SIGKILL ?
The trap is not executed since the KILL signal is received by the sub-process rather than the shell process for which the trap has been set. The effect of the trap on the diagnostics is in the (somewhat arguable) logic in the notify_of_job_status() function.

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"

Why does my bash script take so long to respond to kill when it runs in the background?

(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).

Resources