How can I catch SIGINT without closing xterm? - bash

Here is my script;
#!/bin/bash
trap '' SIGINT
xterm &
wait
I run it and an xterm pops up. Then I focus my keyboard on the originating terminal window and hit ^C. I would like nothing to happen, but instead the child xterm goes away.
(Ideally, I want to install my own trap handler, but this is a baby step)
Using disown after forking xterm detaches the xterm from the parent and then ^C doesn't do anything to the xterm, but then wait doesn't work.
I just want to block SIGINT from getting to xterm.

When you send SIGINT to bash script the signal is propagate to current process in the script, then it executes command in trap. So "wait" is interrupted. You must do that "wait" run again.
Also you must do that all jobs are launched in their own process groups (set -m). From the set man page:
set -m
Monitor mode. Job control is enabled. This option is on by default for interactive shells on systems that support it (see JOB
CONTROL above). Background processes run in a
separate process group and a line containing their exit status is printed upon their completion.
#!/bin/bash
set -m
trap 'R=true' SIGINT
xterm &
while : ; do
R=false
wait
[[ $R == true ]] || break
done
You can see commands that it run with '-x' option in shebang.

Pressing CTRL+C will send SIGINT signal to each process under the same group of the foreground process's. So xterm goes away too. You can use setsid to change the group id of xterm's.
#!/bin/bash
trap 'echo "Caught SIGINT"' SIGINT
setsid xterm &
wait
wait will be interrupted by SIGINT too. So if you want to wait after pressing CTRL+C, you need to wait again according to suggestion of #fbohorquez.
#!/bin/bash
trap 'R=true;echo "Caught SIGINT"' SIGINT
setsid xterm &
while : ; do
R=false
wait
[ $R == false ] && break
done

Related

How can I send a signal without the shell waiting for the currently running program to finish?

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.

bash: Why can't I set a trap for SIGINT in a background shell?

Here's a simple program that registers two trap handlers and then displays them with trap -p. Then it does the same thing, but in a child background process.
Why does the background process ignore the SIGINT trap?
#!/bin/bash
echo "Traps on startup:"
trap -p
echo ""
trap 'echo "Received INT"' INT
trap 'echo "Received TERM"' TERM
echo "Traps set on parent:"
trap -p
echo ""
(
echo "Child traps on startup:"
trap -p
echo ""
trap 'echo "Child received INT"' INT
trap 'echo "Child received TERM"' TERM
echo "Traps set on child:"
trap -p
echo ""
) &
child_pid=$!
wait $child_pid
Output:
$ ./show-traps.sh
Traps on startup:
Traps set on parent:
trap -- 'echo "Received INT"' SIGINT
trap -- 'echo "Received TERM"' SIGTERM
Child traps on startup:
Traps set on child:
trap -- 'echo "Child received TERM"' SIGTERM
SIGINT and SIGQUIT are ignored in backgrounded processes (unless they're backgrounded with set -m on). It's a (weird) POSIX requirement (see
2. Shell Command Language or my SO question Why do shells ignore SIGINT and SIGQUIT in backgrounded processes? for more details).
Additionally, POSIX requires that:
When a subshell is entered, traps that are not being ignored shall be
set to the default actions, except in the case of a command
substitution containing only a single trap command ..
However, even if you set the INT handler in the subshell again after it was reset, the subshell won't be able to receive it because it's ignored (you can try it or you can inspect the signal ignore mask using ps, for example).
Background jobs are not supposed to be tied to the shell that started them. If you exit a shell, they will continue running. As such they shouldn't be interrupted by SIGINT, not by default. When job control is enabled, that is fulfilled automatically, since background jobs are running in separate process groups. When job control is disabled (generally in non-interactive shells), bash makes the asynchronous commands ignore SIGINT.
The relevant parts of the documentation:
Non-builtin commands started 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. Commands run as a result of command substitution ignore the keyboard-generated job control signals SIGTTIN, SIGTTOU, and SIGTSTP.
https://www.gnu.org/software/bash/manual/html_node/Signals.html
To facilitate the implementation of the user interface to job control, the operating system maintains the notion of a current terminal process group ID. Members of this process group (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. Only foreground processes are allowed to read from or, if the user so specifies with stty tostop, write to the terminal. Background processes which attempt to read from (write to when stty tostop is in effect) the terminal are sent a SIGTTIN (SIGTTOU) signal by the kernel’s terminal driver, which, unless caught, suspends the process.
https://www.gnu.org/software/bash/manual/html_node/Job-Control-Basics.html
More on it here.

shell script process termination issue

/bin/sh -version
GNU sh, version 1.14.7(1)
exitfn () {
# Resore signal handling for SIGINT
echo "exiting with trap" >> /tmp/logfile
rm -f /var/run/lockfile.pid # Growl at user,
exit # then exit script.
}
trap 'exitfn; exit' SIGINT SIGQUIT SIGTERM SIGKILL SIGHUP
The above is my function in shell script.
I want to call it in some special conditions...like
when:
"kill -9" fires on pid of this script
"ctrl + z" press while it is running on -x mode
server reboots while script is executing ..
In short, with any kind of interrupt in script, should do some action
eg. rm -f /var/run/lockfile.pid
but my above function is not working properly; it works only for terminal close or "ctrl + c"
Kindly don't suggest to upgrade "bash / sh" version.
SIGKILL cannot be trapped by the trap command, or by any process. It is a guarenteed kill signal, that by it's definition cannot be trapped. Thus upgrading you sh/bash will not work anyway.
You can't trap kill -9 that's the whole point of it, to destroy processes violently that don't respond to other signals (there's a workaround for this, see below).
The server reboot should first deliver a signal to your script which should be caught with what you have.
As to the CTRL-Z, that also gives you a signal, SIGSTOP from memory, so you may want to add that. Though that wouldn't normally be a reason to shut down your process since it may be then put into the background and restarted (with bg).
As to what do do for those situations where your process dies without a catchable signal (like the -9 case), the program should check for that on startup.
By that, I mean lockfile.pid should store the actual PID of the process that created it (by using echo $$ >/var/run/myprog_lockfile.pid for example) and, if you try to start your program, it should check for the existence of that process.
If the process doesn't exist, or it exists but isn't the right one (based on name usually), your new process should delete the pidfile and carry on as if it was never there. If the old process both exists and is the right one, your new process should log a message and exit.

In Bash, how can I run multiple infinitely-running commands and cancel them all with ^C?

I would like to write a script that runs a few different infinitely running commands, e.g.
run_development_webserver.sh
watch_sass_files_and_compile_them.sh
watch_coffeescript_files_and_compile_them.sh
I'd like to run each of them in parallel, and kill them all by hitting ^C. Is this possible, and if so how can I do this?
I'll let Admiral Ackbar answer this one.
#!/bin/bash -e
run_development_webserver.sh &
PIDS[0]=$!
watch_sass_files_and_compile_them.sh &
PIDS[1]=$!
watch_coffeescript_files_and_compile_them.sh &
PIDS[2]=$!
trap "kill ${PIDS[*]}" SIGINT
wait
This starts each of your commands in the background (&), puts their process ids ($!) into an array (PIDS[x]=$!), tells bash to kill them all (${PIDS[*]) when your script gets a SIGINT signal (Ctrl+C), and then waits for all the processes to exit.
And I'll proactively mention that "kill ${PIDS[*]}" expands PIDS when you create the trap; if you change the double quotes (") to single quotes ('), it will be expanded when the trap is executed, which means you can add more processes to PIDS after you set the trap and it will kill them too.
If you have a stubborn process that doesn't want to quit after a Ctrl+C (SIGINT), you may need to send it a stronger kill signal - SIGTERM or even SIGKILL (use this as a last resort, it unconditionally kills the process without giving it a chance to clean up). First, try changing the trap line to this:
trap "kill -TERM ${PIDS[*]}" SIGINT
If it doesn't respond to the SIGTERM, save that process's pid separately, say in STUBBORN_PID, and use this:
trap "kill ${PIDS[*]}; kill -KILL $STUBBORN_PID" SIGINT
Remember, this one won't let the stubborn process clean up, but if it needs to die and isn't, you may need to use it anyway.

Run command when bash script is stopped

How can i, in a bash script, execute a command when the user stops the script (with ctrl - c)?
Currently, i have this:
afplay file.mp3
while true:
do osascript -e "set volume 10"
end
But i would like it to execute killall afplay when the user is finished with it, regardless if it is command-c or another keypress.
trap 'killall afplay' EXIT
Use trap.
trap "kill $pid" INT TERM EXIT
Also avoid killall or pkill, since it could kill unrelated processes (for instance, from another instance of your script, or even a different script). Instead, put the player's PID in a variable and kill only that PID.
You need to put a trap statement in your bash script:
trap 'killall afplay' EXIT
Note however that this won't work if the bash process is sent a KILL signal (9) as it's not possible for processes to intercept that signal.

Resources