shell script not terminating child process after termination of parent process - shell

I have a shell script (run.sh) that runs two commands (jmeter scripts) in parallel. If I terminate the shell script it is not killing the parallel process that got initiated, and they are running in background.
Can I make a shell script that would work in both Windows and Linux and will terminate all the process if Ctrl+C is pressed in the terminal that is executing run.sh?
#!/bin/sh
JmeterFolder=$1
$JmeterFolder/bin/jmeter.bat -n -t one.jmx -j oneLog.log &
$JmeterFolder/bin/jmeter.bat -n -t two.jmx -j twoLog.log &
wait
This is my code.
I have tried:
#!/bin/sh
trap 'stop' 2
stop()
{
kill -9 $pid1 $pid2
}
JmeterFolder=$1
$JmeterFolder/bin/jmeter.bat -n -t one.jmx -j oneLog.log &
pid1=$! &
$JmeterFolder/bin/jmeter.bat -n -t two.jmx -j twoLog.log &
pid2=$! &
wait
but this is not working when I execute it in Windows PowerShell, and don't think this is a right approach.

Wrote an infinite loop to simulate your jemter:
subtask.sh
#!/bin/bash
while true
do
date -R
sleep 1
done
Main script to start the substask and sleep 100 seconds to wait for Ctrl-C:
#!/bin/bash
trap 'kill $subpid; exit' SIGINT
./subtask.sh &
subpid=$!
sleep 100
result:
$ ./main.sh
Mon, 06 May 2019 02:03:32 -0700
Mon, 06 May 2019 02:03:33 -0700
^C$
When I press Ctrl-C "^C", it stopped after printing two lines.
If the main shell has been ended, the subtask's parent process ID will become 1, so it cannot pass signal to background process anymore..
I think the workaround can only be record the subtask PID in a file, and kill pid when you want to end them..

Related

Pause bash script until signal of another one

What is the shortest way to sleep a bash script at a certain location until another script wakes it up to continue it's job?
Mayby using flock -u .. or blocking read on a pipe ?
Say scriptA sleeps and waits for being waken up by scriptB.
One way is, in A, before you sleep, write the pid to some file say scriptA.pid then falling in sleep.
When B is running, at the right moment, you can read the scriptA.pid file, to get the pid of A, then do pkill -P pidofA sleep thus, the sleep sub-process will be killed, and A will continue its execution.
I'm a fan of named pipes (fifo). scriptA.sh:
pipe='/tmp/mypipe'
mkfifo "$pipe"
echo "$0 going to sleep..."
# Should block
read < "$pipe"
echo "$0 continuing"
scriptB.sh
pipe='/tmp/mypipe'
mkfifo "$pipe"
echo "$0 waking other process"
# might block
echo > "$pipe"
echo "$0 exiting"
You will get a mkfifo: /tmp/mypipe: File exists from the second mkfifo, if that bothers you then test for existence first (-e "$pipe"). This does not tidy-up (rm) the fifo, not sure where that should go because timing of the application is critical to where you put that.
You could use the inter process signals: the kill command should be used to send a signal to a process using its pid.
The SIGSTOP signal stops the execution of the process.
The SIGCONT signal resumes the process execution.
The example script below:
stores the pid of the process in a file.
the script sends to its own process the SIGSTOP signal ($$ is the pid of the current bash process).
Hopefully, another process will resume the execution.
Give a try to this:
#!/bin/bash --
printf "%s" $$ > /tmp/aScript.pid
kill -STOP $$ # STOP the execution here
# execution continues here when the SIGCONT signal is received
printf "script %s: received the SIGCONT signal\n" $$
Test in a terminal:
$ ./aScript.sh &
[1] 26444
$ kill -CONT $(cat /tmp/aScript.pid)
script 26444: received the SIGCONT signal
1st method
The running script can stop itself -
$: cat flagfile
#!/usr/bin/bash
echo $$ > /tmp/flagfile.pid
kill -STOP $$
date
$: ./flagfile &
[1] 24679
$: ps -fu $LOGNAME | grep 'flagfile$'
P2759474 24679 24521 0 13:29 pts/0 00:00:00 /usr/bin/bash ./flagfile
[1]+ Stopped ./flagfile
Then any other script can restart it.
$: kill -CONT $(</tmp/flagfile.pid)
$: Wed Dec 12 13:36:01 CST 2018
That last line gave me back a prompt before the background process managed to output the date. :)
2nd method
If a delay is ok, you can have a trap break it out.
This isn't totally stopping the script, but you can set the delay and make it as freindly as you have leeway to wait for it to wake up.
$: cat flagfile
#!/usr/bin/bash
trap 'loop=0' USR1
loop=1
delay=2
echo $$ > /tmp/flagfile.pid
while (( loop )); do sleep $delay; done
date
$: ./flagfile &
[1] 25018
$: ps -fu $LOGNAME | grep 'flagfile$'
P2759474 25018 24521 0 13:42 pts/0 00:00:00 /usr/bin/bash ./flagfile
Wait as long as you like....
$: kill -USR1 $(</tmp/flagfile.pid)
$: Wed Dec 12 13:42:43 CST 2018
[1]+ Done ./flagfile

kill bash script with wait command

A bash script demo.sh
#!/bin/bash
./prog1 &
./prog2 &
wait
Use timeout -s 9 5m demo.sh to run the script.
The script demo.sh used to be without & and wait. I want to know whether timeout will kill prog1 and prog2 when timeout happens. How can I make sure that all subprocesses would be killed?
The forked jobs will be killed when you kill the shell process started by
demo.sh (unless you do something like disown $PID).
You can ensure this happens with kill -0:
./prog1 &
echo P1=$!
./prog2 &
echo P2=$!
you can then kill -0 ${PID1} and kill -0 ${PID2} and ensure that both
commands return with exit status 1, which means "couldn't find process"

How to use trap reliably using Bash running foreground child processes

I have a Bash script that runs a long running process in the foreground. When it receives a SIGQUIT signal, it should perform various cleanup operations such as killing itself and all of its child processes (via kill of process group etc.). A minimal script, that should catch the signal, is shown below (called test_trap.sh):
#!/bin/bash
trap 'echo "TRAP CAUGHT"; exit 1' QUIT # other required signals are omitted for brevity
echo starting sleep
sleep 11666
echo ending sleep
echo done
I would like to send the SIGHUP signal to the process of the test_trap.sh script. However, sending a SIGHUP to the test_trap.sh does not trigger the trap expression, but only when I send the signal to the child sleep 11666 process does the trap fire. Below is a bash session demonstrating this:
bash-4.1$ test_trap.sh &
[1] 19633
bash-4.1$ starting sleep
bash-4.1$ kill -s SIGQUIT 19633
bash-4.1$ jobs
[1]+ Running test_trap.sh &
bash-4.1$ ps -ef --forest --cols=10000 | grep '11666\|test_trap.sh' | grep -v grep
theuser 19633 12227 0 07:40 pts/4 00:00:00 \_ /bin/bash ./test_trap.sh
theuser 19634 19633 0 07:40 pts/4 00:00:00 | \_ sleep 11666
bash-4.1$ kill -s SIGQUIT 19634
bash-4.1$ Quit (core dumped)
TRAP CAUGHT
[1]+ Exit 1 test_trap.sh
bash-4.1$ ps -ef --forest --cols=10000 | grep '11666\|test_trap.sh' | grep -v grep
bash-4.1$
Note that the "sleep 11666" is just a representative process. That process can actually be an interactive subshell (e.g., bash -i).
Why doesn't the parent test_trap.sh process catch the SIGHUP signal? Why would the trap fire only when the process for sleep 11666 was signaled?
I do not want to use uncatchable SIGKILL as I do need to do an assortment of cleanup operations in the trap expression.
This script is intended run on any fairly recent version of any Linux distribution containing Bash (e.g., not Cygwin).
References:
killing Parent process along with child process using SIGKILL
Kill bash and child process
bash must wait for sleep to complete before it can execute the handler. A good workaround is to run sleep in the background, then immediately wait for it. While sleep is uninterruptible, wait is not.
trap 'kill $sleep_pid; echo "TRAP CAUGHT"; exit 1' QUIT
echo starting sleep
sleep 11666 &
sleep_pid=$!
wait
echo ending sleep
echo done
The recording of sleep_pid and using it to kill sleep from the handler are optional.
Actually, bash is receiving the signal, but it is in an uninterruptible state waiting for the sleep command to end. When it ends, bash will react to the signal and execute the trap.
You can replace the long sleep command with a loop of short sleep commands:
while true
do
sleep 1
done
With that, if you send the signal to the bash process, it will react as soon as the currently executing sleep command ends, that is, at most 1 second after it was sent.
Try with the signal SIGINT (the same which is sent by pressing Ctrl+C) instead of SIGKILL. Other signals only work when the bash can process I/O or some other condition.

Sending kill -s USR1 from bash script stops the process while doing the same from terminal does not

There's a nodejs script called mimosa (https://github.com/dbashford/mimosa)
Nodejs uses USR1 to switch the running process to debug mode
Here's how I do it manually
$ cd myproj
$ mimosa watch -s # this runs node /path/to/mimosa watch -s
22:16:03 - Watching /Users/admin/Work/test-mimosa/assets
... # some more output
# check the pid from a different terminal
$ ps aux | grep mimosa
admin 79284 0.7 0.8 3153812 129272 s006 S+ 10:16PM 0:03.57 node /opt/local/bin/mimosa watch -s
# send debug signal from the 2nd terminal
kill -s USR1 79284
# nodejs output in the 1st terminal
Hit SIGUSR1 - starting debugger agent.
debugger listening on port 5858
The same works if I run mimosa as a background process (mimosa watch -s &)
Now I need to automate the process: run mimosa, get its pid, send USR1, wait for user's SIGTERM, kill mimosa:
mimosa watch -s &
pid=$!
echo "mimosa pid: $pid"
trap "echo '\nSTOP'; kill $pid; exit" SIGHUP SIGINT SIGTERM
echo 'send debug'
kill -s USR1 $pid
wait $pid
This script exits immediately, so does the mimosa process (I check it with grep again).
The output in the console
$ ./debug.sh
mimosa pid: 79516
send debug
./debug.sh: line 11: 79516 User defined signal 1: 30 mimosa watch -s
What's wrong, how to fix?
Could mimosa be sending a signal to its own process group when you send the debug signal? That would explain it.
In interactive shells, doing ./program starts program with its own process group. If program does something like kill -s USR1 0, it'll never exit that group.
In non-interactive shells / scripts, doing ./program will start it as a child but in the same process group. If the child does kill -s USR1 0, it'll kill the calling script.
You could do trap 'echo ignoring' USR1 USR2 in your debug.sh in case those are the signals being sent by mimosa.
Alternatively, try turning on job control with set -m before starting mimosa.
See also I have "trap 'echo ignore' USR1" in my called script, why does the calling script get killed?

Set trap in bash for different process with PID known

I need to set a trap for a bash process I'm starting in the background. The background process may run very long and has its PID saved in a specific file.
Now I need to set a trap for that process, so if it terminates, the PID file will be deleted.
Is there a way I can do that?
EDIT #1
It looks like I was not precise enough with my description of the problem. I have full control over all the code, but the long running background process I have is this:
cat /dev/random >> myfile&
When I now add the trap at the beginning of the script this statement is in, $$ will be the PID of that bigger script not of this small background process I am starting here.
So how can I set traps for that background process specifically?
(./jobsworthy& echo $! > $pidfile; wait; rm -f $pidfile)&
disown
Add this to the beginning of your Bash script.
#!/bin/bash
trap 'rm "$pidfile"; exit' EXIT SIGQUIT SIGINT SIGSTOP SIGTERM ERR
pidfile=$(tempfile -p foo -s $$)
echo $$ > "$pidfile"
# from here, do your long running process
You can run your long running background process in an explicit subshell, as already shown by Petesh's answer, and set a trap inside this specific subshell to handle the exiting of your long running background process. The parent shell remains unaffected by this subshell trap.
(
trap '
trap - EXIT ERR
kill -0 ${!} 1>/dev/null 2>&1 && kill ${!}
rm -f pidfile.pid
exit
' EXIT QUIT INT STOP TERM ERR
# simulate background process
sleep 15 &
echo ${!} > pidfile.pid
wait
) &
disown
# remove background process by hand
# kill -TERM ${!}
You do not need trap to just run some command after a background process terminates, you can instead run through a shell command line and add the command following after the background process, separated with semicolon (and let this shell run in the background instead of the background process).
If you still would like to have some notification in your shell script send and trap SIGUSR2 for instance:
#!/bin/sh
BACKGROUND_PROCESS=xterm # for my testing, replace with what you have
sh -c "$BACKGROUND_PROCESS; rm -f the_pid_file; kill -USR2 $$" &
trap "echo $BACKGROUND_PROCESS ended" USR2
while sleep 1
do
echo -n .
done

Resources