trap
reacts to the calling process signals itself. But you must call it before the signal is received. I mean, at the beginning of your script.
Furthermore, if you want to use kill -- -$$
, which sends the signal also to your script, you need to clear the trap before running the kill or you will end with an infinite kill && trap loop.
For example:
#!/bin/bash
exit_script() {
echo "Printing something special!"
echo "Maybe executing other commands!"
trap - SIGINT SIGTERM # clear the trap
kill -- -$$ # Sends SIGTERM to child/sub processes
}
trap exit_script SIGINT SIGTERM
echo "Some other text"
#other commands here
sleep infinity
As explained in the comments, the problem is that the script receives the signal but is waiting for the sleep program to end before processing the received signal. So, you should kill the child processes (the sleep process in this case) in order run the trap action. You can do that with something like the following:
kill -- -$(pgrep script.sh)
Or as stated in the comments:
killall -g script.sh
On a Centos 7 test system via
$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
$ sudo yum install dotnet-sdk-2.1
which results in dotnet-sdk-2.1-2.1.400-1.x86_64
being installed then with the test code
using System;
using System.Diagnostics;
using System.ComponentModel;
namespace myApp {
class Program {
static void Main(string[] args) {
var process = new Process();
process.EnableRaisingEvents = true; // to avoid [defunct] sh processes
process.StartInfo.FileName = "/var/tmp/foo";
process.StartInfo.Arguments = "";
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForExit(10000);
if (process.HasExited) {
Console.WriteLine("Exit code: " + process.ExitCode);
} else {
Console.WriteLine("Child process still running after 10 seconds");
}
}
}
}
and a shell script as /var/tmp/foo
a strace
stalls out and shows that /var/tmp/foo
is run through xdg-open
which on my system does...I'm not sure what, it seems a needless complication.
$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C
$ grep /var/tmp/foo foo
25907 execve("/usr/bin/xdg-open", ["/usr/bin/xdg-open", "/var/tmp/foo"], [/* 37 vars */] <unfinished ...>
...
a simpler solution is to simply exec
a program that in turn can be a shell script that does what you want, which for .NET requires not using the shell:
process.StartInfo.UseShellExecute = false;
with this set the strace
shows that /var/tmp/foo
is being run via a (much simpler) execve(2)
call:
26268 stat("/var/tmp/foo", {st_mode=S_IFREG|0755, st_size=37, ...}) = 0
26268 access("/var/tmp/foo", X_OK) = 0
26275 execve("/var/tmp/foo", ["/var/tmp/foo"], [/* 37 vars */] <unfinished ...>
and that .NET refuses to exit:
$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C^C^C^C^C^C^C^C
because foo
replaces itself with something that ignores most signals (notably not USR2
, or there is always KILL
(but avoid using that!)):
$ cat /var/tmp/foo
#!/bin/sh
exec /var/tmp/stayin-alive
$ cat /var/tmp/stayin-alive
#!/usr/bin/perl
use Sys::Syslog;
for my $s (qw(HUP INT QUIT PIPE ALRM TERM CHLD USR1)) {
$SIG{$s} = \&shandle;
}
openlog( 'stayin-alive', 'ndelay,pid', LOG_USER );
while (1) {
syslog LOG_NOTICE, "oh oh oh oh oh stayin alive";
sleep 7;
}
sub shandle {
syslog LOG_NOTICE, "nice try - @_";
}
daemonize
With a process that disassociates itself from the parent and a shell script that runs a few commands (hopefully equivalent to your intended apt-get update; apt-get upgrade
)
$ cat /var/tmp/a-few-things
#!/bin/sh
sleep 17 ; echo a >/var/tmp/output ; echo b >/var/tmp/output
we can modify the .NET program to run /var/tmp/solitary /var/tmp/a-few-things
process.StartInfo.FileName = "/var/tmp/solitary";
process.StartInfo.Arguments = "/var/tmp/a-few-things";
process.StartInfo.UseShellExecute = false;
which when run causes the .NET program to exit fairly quickly
$ dotnet run
Exit code: 0
and, eventually, the /var/tmp/output
file does contain two lines written by a process that was not killed when the .NET program when away.
You probably should save the output from the APT commands somewhere, and may also need something so that two (or more!) updates are not
trying to be run at the same time, etc. This version does not stop for questions and ignores any TERM
signals (INT
may also need to be ignored).
#!/bin/sh
trap '' TERM
set -e
apt-get --yes update
apt-get --yes upgrade
Best Answer
There are a lot of factors to consider here. Please take this answer with a slight grain of salt as I'm writing all these details from memory. (And please correct me via edits or comments if you spot any mistakes.)
If you background a command with
&
and then the parent shell exits normally, the background command will keep running.However, if you're running interactively, the "backgrounded" command will get blocked when it tries to read from or write to the terminal. (This applies whether or not the parent shell is still running.) If stdout, stdin and stderr are redirected this won't matter.
If you background a command and then e.g. your SSH session is disconnected, you can expect the backgrounded command to terminate. I believe this is because your shell will send SIGHUP to all its child processes, but I'm not 100% certain on the details of that.
If you disown a job (with the bash builtin
disown
) after you background it, then it won't be terminated just because e.g. your SSH session is terminated. However, the aspect of getting blocked when trying to write to terminal or read from terminal still applies (for interactive sessions).The most general way I know of to start a sub-script or process in the background such that it will not be affected no matter what happens to the shell which spawns it (and regardless of whether it's being started interactively or from a script), is:
This assumes you don't care about ANY output of the command to its standard out or standard error, and don't need to feed it any standard input.
If you're happy for the sub-script to inherit the file descriptors of its parent (i.e. terminal is not a problem or you have some other solution), then really all you need should be:
And just a side comment, if you're writing a daemon which is intended to be left running, please (a) use logs and (b) take care of rotating your logs cleanly.