Issues with `trap cleanup INT EXIT` in BASH - bash

I wanted my BASH script to end in a defined way doing some cleanup before exiting.
It's easy to do if the script runs until end, but it's getting tricky if the user is impatient and sends a SIGINT (^C).
So I added a trap cleanup INT EXIT (cleanup is my function to clean things up), and I thought things were OK (as cleanup would be called when the script exits, cleanup itself does not use exit).
But then I started a test adding kill -INT $$; sleep 4 in the middle of the script, and I realized that cleanup is being called on SIGINT, but still the sleep 4 was executed and at the end of my script cleanup was called a second time, something I did not intend.
So I wanted to "reset" the handlers at the end of my cleanup using trap INT EXIT as the manual page said the syntax is "trap [-lp] [[arg] sigspec ...]" (also saying: "If arg is absent (and there is a single sigspec) or -, each specified signal is reset to its original disposition (the value it had upon entrance to the shell).").
Interestingly that did not work as intended, so I used trap '' INT EXIT instead (The manual says: "If arg is the null string the signal specified by each sigspec is ignored by the shell and by the commands it invokes.").
It would be a nice sub-question how to do it correctly, but let's ignore that right now.
If I modify my trap to trap cleanup INT, then the cleanup is executed immediately when receiving the SIGINT, and not when the script exits after the sleep eventually (SIGINT does not cause the script to exit early).
If I modify my trap to trap cleanup EXIT, then the cleanup is executed immediately when receiving the SIGINT, and the script ends after cleanup returned.
So the question is: Does trap cleanup INT EXIT make any sense (for cleanup purposes)?
It seems to me that EXIT includes the exits caused by any signal, too (I'm unsure whether that has been the case always).
Contrary trapping SIGINT would perform cleanup actions without actually causing the script to exit.
Is there a general agreed-on "cleanup trap pattern"?
(There is a similar question in bash robustness: what is a correct and portable way to trap for the purpose of an "on exit" cleanup routine?, but it has no good answer)

The shell does not exit when a signal for which a trap has been set is received. So, the answer is no; trap cleanup INT EXIT does not make any sense for cleanup purposes, as it prevents SIGINT from interrupting the execution of the program, and hooks the cleanup routine to an event that doesn't warrant a cleanup anymore.
Not sure how agreed-upon, but this is how I do an automatic cleanup on normal or signal-driven termination:
cleanup() {
# do the cleanup
}
trap cleanup EXIT

Related

stop currently running bash script lazily/gracefully

Say I have a bash script like this:
#!/bin/bash
exec-program zero
exec-program one
the script issued a run command to exec-program with the arg "zero", right? say, for instance, the first line is currently running. I know that Ctrl-C will halt the process and discontinue executing the remainder of the script.
Instead, is there a keypress that will allow the current-line to finish executing and then discontinue the script execution (not execute "exec-program one") (without modifying the script directly)? In this example it would continue running "exec-program zero" but after would return to the shell rather than immediately halting "exec-program zero"
TL;DR Something runtime similar to "Ctrl-C" but more lazy/graceful ??
In the man page, under SIGNALS section it reads:
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.
This is exactly what you're asking for. You need to set an exit trap for SIGINT, then run exec-program in a subshell where SIGINT is ignored; so that it'll inherit the SIG_IGN handler and Ctrl+C won't kill it. Below is an implementation of this concept.
#!/bin/bash -
trap exit INT
foo() (
trap '' INT
exec "$#"
)
foo sleep 5
echo alive
If you hit Ctrl+C while sleep 5 is running, bash will wait for it to complete and then exit; you will not see alive on the terminal.
exec is for avoiding another fork() btw.

bash robustness: what is a correct and portable way to trap for the purpose of an "on exit" cleanup routine?

When I create temporary files that I want removed when a script exits, I typically set a trap on EXIT. something like this:
function cleanup ()
{
if [[ -d "$mytmp" ]]; then rm -rf --one-file-system -- "$mytmp" || :; fi
}
trap cleanup EXIT
...
I recently stumbled upon another script which sets things up slightly differently. It sets up a trap on EXIT, but also on actual signals, i.e.:
function cleanup () { : ... same as above ...; }
trap cleanup EXIT HUP INT QUIT TERM
I'm questioning whether the additional signals in the list are superfluous, or a strict improvement. Are there versions of bash that don't run the EXIT trap on certain signals?
In my experience, CTRL-C (SIGINT) a script will typically also invoke the EXIT trap, even without explicitly trapping on INT. Maybe I've always been lucky and it's just that a child gets killed by SIGINT first, and then the parent script exited with an error (because of set -e or another reason).
What's the best cleanup idiom, in terms of correctness and maybe portability?
Update:
This is tangential to my question, but one portability improvement, pointed out in the comments, is to define functions using the short form: cleanup () { :; } rather than function cleanup () { :; }. Thanks!
I've tested this quite recently on an old bash version (3.2, the one on MacOS from 2007...) and even there it's superfluous.
One could, however, argue that it's there for readability, to signal the reader: "there's no special behavior for any of the signals, just clean up and exit.", because if you have two trap statements referring to the same signal, the one which executed last will override.

Basic signal communication

I have a bash script, its contents are:
function foo {
echo "Foo!"
}
function clean {
echo "exiting"
}
trap clean EXIT
trap foo SIGTERM
echo "Starting process with PID: $$"
while :
do
sleep 60
done
I execute this on a terminal with:
./my_script
And then do this on another terminal
kill -SIGTERM my_script_pid # obviously the PID is the one echoed from my_script
I would expect to see the message "Foo!" from the other terminal, but It's not working. SIGKILL works and the EXIT code is also executed.
Using Ctrl-C on the terminal my_script is running on triggers foo normally, but somehow I can't send the signal SIGTERM from another terminal to this one.
Replacing SIGTERM with any other signal doesn't change a thing (besides Ctrl-C not triggering anything, it was actually mapped to SIGUSR1 in the beginning).
It may be worth mentioning that just the signal being trapped is not working, and any other signal is having the default behaviour.
So, what am I missing? Any clues?
EDIT: I also just checked it wasn't a privilege issue (that would be weird as I'm able to send SIGKILL anyway), but it doesn't seem to be that.
Bash runs the trap only after sleep returns.
To understand why, think in C / Unix internals: While the signal is dispatched instantly to bash, the corresponding signal handler that bash has setup only does something like received_sigterm = true.
Only when sleep returns, and the wait system call which bash issued after starting the sleep process returns also, bash resumes its normal work and executes your trap (after noticing received_sigterm).
This is done this way for good reasons: Doing I/O (or generally calling into the kernel) directly from a signal handler generally results in undefined behaviour as far as I know - although I can't tell more about that.
Apart from this technical reason, there is another reason why bash doesn't run the trap instantly: This would actually undermine the fundamental semantics of the shell. Jobs (this includes pipelines) are executed strictly in a sequential manner unless you explicitly mess with background jobs.
The PID that you originally print is for the bash instance that executes your script, not for the sleep process that it is waiting on. During sleep, the signal is likely to be ignored.
If you want to see the effect that you are looking for, replace sleep with a shorter-lived process like ps.
function foo {
echo "Foo!"
}
function clean {
echo "exiting"
}
trap clean EXIT
trap foo SIGTERM
echo "Starting process with PID: $$"
while :
do
ps > /dev/null
done

How do I stop a signal from killing my Bash script?

I want an infinite loop to keep on running, and only temporarily be interrupted by a kill signal. I've tried SIGINT, SIGUSR1, SIGUSR2. All of them seem to halt the loop. I even tried SIGINFO, but that wasn't supported by Linux.
#!/bin/bash
echo $$ > /tmp/pid # Save the pid
function do_something {
echo "I am doing stuff" #let's do this now, and go back to doing the thing that is to be done over and over again.
#exit
}
while :
do
echo "This should be done over and over again, but always wait for someething else to be done in between"
trap do_something SIGINT
while `true`
do
sleep 1 #so we're waiting for that other thing.
done
done
My code runs the function once, after getting a INT signal from another script, but then never again. It halts.
EDIT: Although I accidentally put en exit at the end of the function, here on Stack Overflow, I didn't in the actual code I used. Either way, it made no difference. The solution is SIGTERM as described by Tiago.
I believe you're looking for SIGTERM:
Example:
#! /bin/bash
trap -- '' SIGINT SIGTERM
while true; do
date +%F_%T
sleep 1
done
Running this example cTRL+C won't kill it nor kill <pid> you can however kill it with kill -9 <pid>.
If you don't want CTRL+Z to interrupt use: trap -- '' SIGINT SIGTERM SIGTSTP
trap the signal, then either react to it appropriately, in the function associate with the trap, or ignore it by for example associate : as command to get executed when the signal occurs.
to trap signals, bash knows the trap command
Reset trap to former action by executing trap with signal name only.
Therefore you want to (i think that's what you say you want with "only temporarily be interrupted by a kill signal"):
trap the signal at the begin of your script: trap signal custom_action
just before you want the signal to allow interrupting your script, execute: trap signal
At the end of that phase, trap again by: signal custom_action
to specify signals, you can also use their respective signal numbers. A list of signal names is printed with the command:
trap -l
the default signal sent by kill is SIGTERM (15), unless you specify a different signal after the kill command
don't exit in your do_something function. Simply let the function return to the section in your code where it was interrupted when the signal occured.
The mentioned ":" command has another potential use in your script, if you feel thusly inclined:
while :
do
sleep 1
done
can be an alternative to "while true" - no backticks needed for that, btw.
You just want to ignore the exit status.
If you want your script to keep running and not exit, without worrying about handling traps.
(my_command) || true
The parentheses execute that command in a subshell. The true is for compatibility with set -e, if you use it. It simply overrides the status to always report a success.
See the source.
I found this question to be helpful:
How to run a command before a Bash script exits?

Shell script execution without interruption

How can a sequence of commands in a shell script be executed without interruption by any other processes?
You mean, without being preempted ? No way. The kernel scheduler is free to choose which task to executed at any time.
However, on Linux, you can set a ``real-time'' priority (i.e., SCHED_FIFO or SCHED_RR) to be sure that the script won't be interrupted to execute lower-priority tasks.
If you are asking how to make a shell process ignore interrupts, or other signals, the answer is via the trap command.
trap "" 2
or:
trap "" INT
To cancel that behaviour, don't list anything in the string:
trap 2
trap INT
If you need to remove temporary files on interrupt (and related signals), you can use something like:
tmp=$(mktemp ${TMPDIR:-/tmp}/name.XXXXXX)
trap "rm -f $tmp; exit 1" 0 1 2 3 13 15 # EXIT HUP INT QUIT PIPE TERM
...do operations using temporary files...
rm -f $tmp
trap 0 # Cancel the exit trap
The set of signals shown is a pretty comprehensive set, covering most normal events. If you get sent SIGKILL (kill -9), the temporary will be left around — there is nothing you can do. The mktemp command creates the file safely (see Why do we need mktemp?), but actually leaves a tiny window of opportunity between when the file is created and the trap is set for the script to be interrupted and leave the temporary file lying around.

Resources