Bash script run with timeout won't exit on SIGINT - bash

I have a bash script which calls another bash script within a for loop (under a timeout condition) in the following format:
#!/bin/bash
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
INNER_SCRIPT_PATH="./inner_script.sh"
for file in "$SAMPLEDIR"/*
do
if [[ "${file: -4}" == ".csv" ]]; then
CSVPATH="$file"
CSVNAME=${CSVPATH##*/} # extract file name
CSVNAME=${CSVNAME%.*} # remove extension
timeout -k 10s 30m bash "$INNER_SCRIPT_PATH"
fi
done
wait
Pressing Ctrl-C does not quit out of all the processes, and I have a feeling there is probably something wrong with the way I'm calling the inner bash script here (especially with timeout). Would appreciate feedback on how to make this better!

The issue is with the timeout command, that makes your script immune to Ctrl+C invocation. Since by default timeout runs in its own process group and not in the foreground process group, it is immune to the signals invoked from an interactive terminal.
You can run it with --foreground to accept signals from an interactive shell. See timeout Man page

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.

How can I catch SIGINT without closing xterm?

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

Shell script do timed task stop and repeat?

I am working with programs that use CTRL-C to stop a task, and what I want to do is to run that task for certain number of minutes and then have it stop like CTRL-C was pressed. The reason why I want it to stop like ctrl+c was pressed is because it auto saves when you stop the program instead of killing it and possibly losing the saved data.
edit; I don't want to use cron unless if it stops my script it will have the program save the data, I am hoping to accomplish this inside the shell script.
The trap statement catches these sequences and can be programmed to execute a list of commands upon catching those signals.
-#!/bin/bash
trap "echo Saving Data" SIGINT
while :
do
sleep 60
done
For Information on Traps : http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html
Using timeout command to send SIGINT after 60 seconds:
timeout --signal=INT 60 /path/to/script.sh params
If you need to intercept ctrl+c, you should use the trap builtin, like this :
cleanup(){ # do something...; }
trap 'cleanup' 2
# rest of the code
2 on trap line is the SIGINT signal sended by ctrl+c, see man 7 signals
Try the following:
#!/bin/bash
set -m
/path/to/script.sh params &
set +m
bg_pid=$!
sleep 60
kill -2 $bg_pid
This should allow you to send SIGINT to a backgrounded process using Job Control and the set builtin

Terminate running commands when shell script is killed [duplicate]

This question already has answers here:
What's the best way to send a signal to all members of a process group?
(34 answers)
Closed 6 years ago.
For testing purposes I have this shell script
#!/bin/bash
echo $$
find / >/dev/null 2>&1
Running this from an interactive terminal, ctrl+c will terminate bash, and the find command.
$ ./test-k.sh
13227
<Ctrl+C>
$ ps -ef |grep find
$
Running it in the background, and killing the shell only will orphan the commands running in the script.
$ ./test-k.sh &
[1] 13231
13231
$ kill 13231
$ ps -ef |grep find
nos 13232 1 3 17:09 pts/5 00:00:00 find /
$
I want this shell script to terminate all its child processes when it exits regardless of how it's called. It'll eventually be started from a python and java application - and some form of cleanup is needed when the script exits - any options I should look into or any way to rewrite the script to clean itself up on exit?
I would do something like this:
#!/bin/bash
trap : SIGTERM SIGINT
echo $$
find / >/dev/null 2>&1 &
FIND_PID=$!
wait $FIND_PID
if [[ $? -gt 128 ]]
then
kill $FIND_PID
fi
Some explanation is in order, I guess. Out the gate, we need to change some of the default signal handling. : is a no-op command, since passing an empty string causes the shell to ignore the signal instead of doing something about it (the opposite of what we want to do).
Then, the find command is run in the background (from the script's perspective) and we call the wait builtin for it to finish. Since we gave a real command to trap above, when a signal is handled, wait will exit with a status greater than 128. If the process waited for completes, wait will return the exit status of that process.
Last, if the wait returns that error status, we want to kill the child process. Luckily we saved its PID. The advantage of this approach is that you can log some error message or otherwise identify that a signal caused the script to exit.
As others have mentioned, putting kill -- -$$ as your argument to trap is another option if you don't care about leaving any information around post-exit.
For trap to work the way you want, you do need to pair it up with wait - the bash man page says "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." wait is the way around this hiccup.
You can extend it to more child processes if you want, as well. I didn't really exhaustively test this one out, but it seems to work here.
$ ./test-k.sh &
[1] 12810
12810
$ kill 12810
$ ps -ef | grep find
$
Was looking for an elegant solution to this issue and found the following solution elsewhere.
trap 'kill -HUP 0' EXIT
My own man pages say nothing about what 0 means, but from digging around, it seems to mean the current process group. Since the script get's it's own process group, this ends up sending SIGHUP to all the script's children, foreground and background.
Send a signal to the group.
So instead of kill 13231 do:
kill -- -13231
If you're starting from python then have a look at:
http://www.pixelbeat.org/libs/subProcess.py
which shows how to mimic the shell in starting
and killing a group
#Patrick's answer almost did the trick, but it doesn't work if the parent process of your current shell is in the same group (it kills the parent too).
I found this to be better:
trap 'pkill -P $$' EXIT
See here for more info.
Just add a line like this to your script:
trap "kill $$" SIGINT
You might need to change 'SIGINT' to 'INT' on your setup, but this will basically kill your process and all child processes when you hit Ctrl-C.
The thing you would need to do is trap the kill signal, kill the find command and exit.

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