Trap ctrl-c, run a function, but don't exit subprocess? - bash

It's actually a Scala REPL launched as a child process from a bash script.
I want to change what ctrl-c does, but otherwise not interfere with the scala REPL.
function ctrl_c_handler() {
# do some stuff, but don't exit
}
trap ctrl_c_handler SIGINT
scala
Interestingly ignoring the signal works fine trap '' SIGINT. ctrl-c can't kill my REPL, but if I actually attach a function I haven't been able to prevent the process from exiting.
What are the options? The scala REPL needs to be in the foreground. It would be ok for the ctrl_c_handler to launch a background process.

Bash will see neither terminal input nor signals generated by the terminal (such as SIGINT) once the subprocess is started, because the subprocess becomes the only member of the terminal's foreground process group. Bash only sees the terminal again once the subprocess terminates.
Ignoring SIGINT in bash works because subprocesses inherit the dispositions of signals which have not been assigned signal handlers.
You could reassign SIGINT to a different keyboard symbol (or to no keyboard symbol) using stty; the subprocess will inherit that setting, too. That will cause ctrl-C to become an ordinary character so it won't interrupt the subprocess. Unfortunately, it won't interrupt bash either, so that's not really a solution.
You can create a subprocess over which you exert more control by creating a pseudoterminal (pty) and attaching the subprocess to the slave end of the pseudo-tty. Then you can pass input you read from the terminal to the subprocess by writing the data to the master end of the pty, and pass output from the subprocess back to the terminal by reading from the master end and writing to the terminal. In this configuration, you can trap SIGINT in the master process and not pass it through to the subprocess. However, you would almost certainly need to write this in some language (not necessarily C) which gives you access to system facilities; it might be possible in bash, but it would be quite a hack. Read man pty for a lot more information on where to start.

Related

Ctrl-C doesn't always terminate a shell script

I have two scenarios:
#!/usr/bin/env bash
sleep infinity
# When I type Ctrl-C here, "sleep" command and script are stopped so I didn't see "End"
echo End
#!/usr/bin/env bash
docker exec container-id sleep infinity
# When I type Ctrl-C here, "docker exec" command is stopped but script continued so I saw "End"
echo End
Why the difference in behaviour?
That's how bash behaves when its process group receives a SIGINT but the program currently running on the foreground terminates normally.
The rationale for this behavior is given here as follows:
The basic idea is that the user intends a keyboard-generated SIGINT to go
to the foreground process; that process gets to decide how to handle it;
and bash reacts accordingly. If the process dies to due SIGINT, bash acts
as if it received the SIGINT; if it does not, bash assumes the process
handled it and effectively ignores it.
Consider a process (emacs is the usual example) that uses SIGINT for its
own purposes as a normal part of operation. If you run that program in a
script, you don't want the shell aborting the script unexpectedly as a
result.

If I run a script with nohup, which in turn calls another script, is the other script effected by nohup?

So let's say I have a script called script1 who somewhere in the code calls script2:
...
./script2
...
And let's say I run script1 as such:
nohup ./script1
Will script2 be effected by the nohup?
The nohup command detaches the command from the controlling terminal from which it is being run. Child processes inherit the environment from the parent process, thus are also detached.
The name of the command comes from "NO Hang-UP", refererring to SIGHUP signal. The signal is used to notify processes that the terminal is closed, and no more input/output is possible. The signal is sent only to the processes which are attached to the terminal (read from and/or write to; e.g. interactive user input/output). What nohup tool does, is to simply redirect input/output of the given command away from the terminal, thus making sure it will not receive the SIGHUP when the terminal closes. On the Unix-like OSs, the child processes automatically inherit the I/O redirection from the parent process.

Background process getting killed when its parent is terminated?

I have code that looks something like this
function doTheThing{
# a potentially infinite while loop...
}
# other stuff...
doTheThing &
trap "kill $!" SIGINT SIGTERM
Strangely, when I ctrl-C out of the parent process before the loop is done, I get a message that the process doesn't exist. Furthermore, if I get rid of the trap, I can't find the process with a ps -aF. It looks like the background process is getting killed when its parent is terminated, but my understanding was that wasn't supposed to happen. I just want to make sure that I can safely leave out the trap and not leave zombie processes everywhere.
The POSIX specification says that when you type the interrupt character (normally Control-C) the SIGINT is sent to the foreground process group. So as long as the background process is running in the same process group as the script that invoked it, it will receive the signal at the same time as the script process.
Shells generally use process groups to implement job control, and by default this is only enabled in interactive shells, not shells running scripts. There's no standard way to run a function in its own process group, but you could use setsid to run it in a new session, which is an even higher level of grouping than process groups. Then it wouldn't receive the interrupt.
You might still want to write a trap command that kills the function on EXIT, though.
doTheThing&
trap "kill $!" EXIT
since exiting the script doesn't automatically kill the rest of the process group.

bash restart sub-process using trap SIGCHLD?

I've seen monitoring programs either in scripts that check process status using 'ps' or 'service status(on Linux)' periodically, or in C/C++ that forks and wait on the process...
I wonder if it is possible to use bash with trap and restart the sub-process when SIGCLD received?
I have tested a basic suite on RedHat Linux with following idea (and certainly it didn't work...)
#!/bin/bash
set -o monitor # can someone explain this? discussion on Internet say this is needed
trap startProcess SIGCHLD
startProcess() {
/path/to/another/bash/script.sh & # the one to restart
while [ 1 ]
do
sleep 60
done
}
startProcess
what the bash script being started just sleep for a few seconds and exit for now.
several issues observed:
when the shell starts in foreground, SIGCHLD will be handled only once. does trap reset signal handling like signal()?
the script and its child seem to be immune to SIGINT, which means they cannot be stopped by ^C
since cannot be closed, I closed the terminal. The script seems to be HUP and many zombie children left.
when run in background, the script caused terminal to die
... anyway, this does not work at all. I have to say I know too little about this topic.
Can someone suggest or give some working examples?
Are there scripts for such use?
how about use wait in bash, then?
Thanks
I can try to answer some of your questions but not all based on what I
know.
The line set -o monitor (or equivalently, set -m) turns on job
control, which is only on by default for interactive shells. This seems
to be required for SIGCHLD to be sent. However, job control is more of
an interactive feature and not really meant to be used in shell scripts
(see also this question).
Also keep in mind this is probably not what you intended to do
because once you enable job control, SIGCHLD will be sent for every
external command that exists (e.g. every time you run ls or grep or
anything, a SIGCHLD will fire when that command completes and your trap
will run).
I suspect the reason the SIGCHLD trap only appears to run once is
because your trap handler contains a foreground infinite loop, so your
script gets stuck in the trap handler. There doesn't seem to be a point
to that loop anyways, so you could simply remove it.
The script's "immunity" to SIGINT seems to be an effect of enabling
job control (the monitor part). My hunch is with job control turned on,
the sub-instance of bash that runs your script no longer terminates
itself in response to a SIGINT but instead passes the SIGINT through to
its foreground child process. In your script, the ^C i.e. SIGINT
simply acts like a continue statement in other programming languages
case, since SIGINT will just kill the currently running sleep 60,
whereupon the while loop will immediately run a new sleep 60.
When I tried running your script and then killing it (from another
terminal), all I ended up with were two stray sleep processes.
Backgrounding that script also kills my shell for me, although
the behavior is not terribly consistent (sometimes it happens
immediately, other times not at all). It seems typing any keys other
than enter causes an EOF to get sent somehow. Even after the terminal
exits the script continues to run in the background. I have no idea
what is going on here.
Being more specific about what you want to accomplish would help. If
you just want a command to run continuously for the lifetime of your
script, you could run an infinite loop in the background, like
while true; do
some-command
echo some-command finished
echo restarting some-command ...
done &
Note the & after the done.
For other tasks, wait is probably a better idea than using job control
in a shell script. Again, it would depend on what exactly you are trying
to do.

How to kill all children of the current shell on interrupt?

My scripts cdist-deploy-to and cdist-mass-deploy (from cdist configuration management) run interactively (i.e. are called by a user).
These scripts call a lot of scripts, which again call some scripts:
cdist-mass-deploy ...
cdist-deploy-to ...
cdist-explorer-run-global ...
cdist-dir ....
What I want is to exit / kill all scripts, as soon as cdist-mass-deploy is either stopped by control C (SIGINT) or killed with SIGTERM.
cdist-deploy-to can also be called interactively and should exhibit the same behaviour.
Using ps -ef... and co variants to find out all processes with the ppid looks like it could be quite unportable. Using $! does not work as in the deeper levels the children are no background processes.
I tried using the following code:
__cdist_kill_on_interrupt()
{
__cdist_tmp_removal
kill 0
exit 1
}
trap __cdist_kill_on_interrupt INT TERM
But this leads to ugly Terminated messages as well as to a segfault in the shells (dash, bash, zsh) and seems not to stop everything instantly anyway:
# cdist-mass-deploy -p ikq04.ethz.ch ikq05.ethz.ch
core: Waiting for cdist-deploy-to jobs to finish
^CTerminated
Terminated
Terminated
Terminated
Segmentation fault
So the question is, how to cleanly exit including all (sub-)children in a portable manner (bourne shell, no csh support needed)?
You don't need to handle ^C, that will result in a signal being sent to the whole process group, which will kill all the processes that are not in the background. So you don't need to catch INT.
The only reason you get a Terminated when you kill them is that kill sends TERM by default, but that's reasonable if you are handling a TERM in the first place. You could use kill -INT 0 if you want to avoid the messages.
(responding with extra info)
If the child processes are run in the background, you can get their process ids just after you start them, using the $! special shell variable. Gather these together in a variable and just kill them all when you need to terminate.

Resources