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
Related
I have a bash script that among other things, launches a background process. I use a function that setups some configuration for the process, launches it, checks it started correctly, and returns its PID, which is used later to kill the subprocess. The sample code below has the same structure but simplified logic:
function launcher(){
sleep 30 &
echo $!
PID=$(launcher)
echo $PID
kill $PID
The issue I'm facing is that the subshell that executes the launcher function does not return until the sleep command ends. Therefore the echo $PID statement is not executed until the subshell ends.
what surprises me is that if I check the sleep command, it does not have the script as parent id:
UID PID PPID C STIME TTY STAT TIME CMD
user 20135 1 0 18:39 pts/8 S+ 0:00 sleep 30
How can I start the sleep & in the background to allow the subshell to end before it ends?
Note: Please notice in my case, the background process will never end until I kill it, so I need the subshell to end get the PID. Also notice in my real code, the logic of the launcher function is quite complex and I'm running it as a subshell to isolate the main process from it.
Thanks in advance
It happens that the problem was about stdin because the main shell was reading from the subshell's stdout, which is inherited by the background process. Just redirecting the stdout when invoking the background process makes it work as expected.
sleep 100 > /dev/null &
I'm not sure if this gets it done but
function launcher(){
echo "start launching"
sleep 100 &
echo "end launching"
}
launcher
PID=$!
# Here $PID is the process id of `sleep`
echo $PID
kill $PID
Without the kill, this runs the sleep command forked and the shell script ends leaving the sleep command running with the pid set in PID which means you could kill it later or not.
Is this what you need? If not, can you clarify what you're expecting?
I also noticed that if the parent script stays alive, the PPID of the sleep process is correct and stays in tact.
# sleeper_test.sh
#!/bin/bash
function launcher(){
echo "start launching"
sleep 100 &
echo "end launching"
}
launcher
PID=$!
# Here $PID is the process id of `sleep`
echo $PID
sleep 10
#kill $PID
$ ps -ef | grep sleep
501 13748 5471 0 1:54PM ttys000 0:00.00 /bin/bash ./sleeper_test.sh
501 13749 13748 0 1:54PM ttys000 0:00.00 sleep 100 <- child correctly tied to the parent sh script
501 13750 13748 0 1:54PM ttys000 0:00.00 sleep 10
$ ps -ef | grep sleep
501 13749 1 0 1:54PM ttys000 0:00.00 sleep 100 <- since the parent ended - it's parent becomes the root process parent
``
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..
I have this kind of script program.sh :
#!/bin/bash
catch_sigint()
{
echo "program interrupted"
exit
}
trap catch_sigint INT TERM
catch_exit()
{
echo "program Exiting"
}
trap catch_exit EXIT
#rest of my program...
echo "program Running"
sleep 2
echo "program normal exit"
that is called by another script like that:
#!/bin/bash
catch_sigint()
{
echo "script interrupted"
exit
}
trap catch_sigint INT TERM
catch_exit()
{
echo "script Exiting"
kill -TERM $prog_pid
wait $prog_pid
echo "script END"
}
trap catch_exit EXIT
./program.sh &> >(tee -a log) &
prog_pid=$!
ps $prog_pid
# my code...
sleep 5
echo "script normal exit"
I can see on the terminal and in the log file that my program.sh is "Running". However I can't see that it is exiting. Indeed, when I do not do the redirection to tee, I can well see on the terminal that it is "Exiting", so the kill by pid is working.
So I think the tee redirection is killed at the same time than the program.sh but what I would like is to see the whole logs in the terminal and the log file. How can I do that ?
Update on 13 March 2019
I have updated the code to show the real use case that really show the issue. In fact, each script has its signal trap. So I guess that the tee redirection is interrupted as the rest of the program that's why it shows nothing. My output with interruption:
program Running
PID TTY STAT TIME COMMAND
4922 pts/1 S+ 0:00 /bin/bash ./program.sh
^Cscript interrupted
script Exiting
script END
My output without interruption:
program Running
PID TTY STAT TIME COMMAND
4915 pts/1 S+ 0:00 /bin/bash ./program.sh
program normal exit
program Exiting
script Exiting
./script.sh: ligne 13 : kill: (4915) - Aucun processus de ce type
script normal exit
script END
I modified your 2 scripts mimiking the run time by sleeps and printing the batch process. You can see the "Running" and the "Exiting" from the output. Probably, in your case the calling script kills the program.sh to quickly?
program.sh
#!/bin/bash
catch_sigint()
{
exit
}
trap catch_sigint INT TERM
catch_exit()
{
echo "Exiting"
}
trap catch_exit EXIT
#rest of my program...
echo "Running"
sleep 2
script.sh
#!/bin/bash
./program.sh &> >(tee -a log) &
prog_pid=$!
ps $prog_pid
# my program...
sleep 1
# end of the program ...
kill -TERM $prog_pid
wait $prog_pid
As a result I get:
$ ./script.sh
Running
PID TTY STAT TIME COMMAND
3081 pts/4 S+ 0:00 /bin/bash ./program.sh
Exiting
Hope this helps.
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.
(or How to kill the child process)?
inotifywait -mqr --format '%w %f %e' $feedDir | while read dir file event
do
#something
done &
echo $! #5431
ps eg:
>$ ps
PID TTY TIME CMD
2867 pts/3 00:00:02 bash
5430 pts/3 00:00:00 inotifywait
5431 pts/3 00:00:00 bash
5454 pts/3 00:00:00 ps
It seems if I kill 5431 then 5430 (inotifywait) will be left running, but if I kill 5430 then both processes die. I don't suppose I can reliably assume that the pid of inotifywait will always be 1 less than $!?
When we run a pipe, each command is executed in a separated process. The interpreter waits for the last one but if we use ampersand (&).
cmd1 | cmd2 &
The pid of processes will be probably close, but we cannot assume it reliably. In the case where the last command is a bash reserved word as while, it creates a dedicated bash (that's why your 'dir', 'file' variables won't exist after the done keyword). Example:
ps # shows one bash process
echo "azerty" | while read line; do ps; done # shows one more bash
When the first command exits, the second one will terminate because the read on the pipe return EOF.
When the second command exits, the first command will be terminated by the signal SIGPIPE (write on a pipe with no reader) when it tries to write to the pipe. But if the command waits indefinitely... it is not terminated.
echo "$!" prints the pid of the last command executed in background. In your case, the bash process that is executing the while loop.
You can find the pid of "inotifywait" with the following syntax. But it's uggly:
(inotifywait ... & echo "$!">inotifywait.pid) | \
while read dir file event
do
#something
done &
cat inotifywait.pid # prints pid of inotifywait
If you don't want the pid, but just be sure the process will be terminated, you can use the -t option of inotifywait:
(while true; do inotifywait -t 10 ...; done)| \
while read dir file event
do
#something
done &
kill "$!" # kill the while loop
None of this solution are nice. What is your real achievement? Maybe we can find a more elegant solution.
If your goal is to make sure all of the children can be killed or interrupted elegantly. If you're using BusyBox's Ash, you don't have process substitution. If you don't want to use an fd either, check out this solution.
#!/bin/sh
pid=$$
terminate() {
pkill -9 -P "$pid"
}
trap terminate SIGHUP SIGINT SIGQUIT SIGTERM
# do your stuff here, note: should be run in the background {{{
inotifywait -mqr --format '%w %f %e' $feedDir | while read dir file event
do
#something
done &
# }}}
# Either pkill -9 -P "$pid" here
wait
# or pkill -9 -P "$pid" here
Or in another shell:
kill <pid ($$)>