I want to write 2 scripts that send a signal each other, like a ping-pong match, but with a signal, not a ball.
First script:
#!/bin/bash
PATH=${PATH}:"/home/cosimo/Università/Sistemi Operativi/scripts"
exec player2pp.sh $$ &
trap "kill -SIGUSR1 $pidp2" SIGUSR1
sleep 2
Second script (player2pp.sh):
#!/bin/bash
trap "kill -SIGUSR1 $1" SIGUSR1
sleep 2
kill -SIGUSR1 $1
sleep 2
I got this error in player2pp.sh:
kill: no corresponding process.
What am I doing wrong?
Some problems:
you're launching the player2 script in the background, so you don't need exec
you don't store the PID of the player2 process anywhere
the first script launches player2, sleeps, then exits. You need to start some kind of infinite loop to avoid exiting.
if you kill the first script with Ctrl-C, the second script is left running in the background
Additionally, I would write a function for the trap so it's easier to do some logging:
player1pp.sh
#!/bin/bash
cd "/home/cosimo/Università/Sistemi Operativi/scripts"
./player2pp.sh $$ &
pidp2=$!
_ping() {
echo "ping! killing $pidp2"
kill -SIGUSR1 $pidp2
}
trap _ping SIGUSR1
trap "kill $pidp2" EXIT
while true; do sleep 2; done
player2pp.sh
#!/bin/bash
pidp1=$1
_pong() {
echo "pong! killing $pidp1"
kill -SIGUSR1 $pidp1
}
trap _pong SIGUSR1
_pong # start the game
while true; do sleep 2; done
For fun, add some randomness:
Player 1
#!/bin/bash
cd "/home/cosimo/Università/Sistemi Operativi/scripts"
./player2pp.sh $$ &
opponent=$!
ping() {
sleep=$((RANDOM % 5))
echo "ping! killing $opponent in $sleep"
sleep $sleep
kill -USR1 $opponent
}
trap ping USR2
cleanup () {
kill -0 $opponent && kill $opponent
}
trap cleanup EXIT
ping
while :; do :; done
Player 2
#!/bin/bash
opponent=$1
pong() {
sleep=$((RANDOM % 5))
echo "pong! killing $opponent in $sleep"
sleep $sleep
kill -USR2 $opponent
}
trap pong USR1
cleanup () {
kill -0 $opponent && kill $opponent
}
trap cleanup EXIT
while :; do :; done
Related
I have a function in bash, call it "timer", that simply displays number of seconds elapsed. Presently, it runs in a separate process, and the parent process kills it when it is done.
I wish the function to to trap a signal somehow and exit gracefully, but I have no idea how. Here is an example script as it is now:
#!/bin/bash
function timer () {
t0=$(date +%s)
while true ; do
t=$(date +%s)
echo -en "\r$(($t - $t0))"
done
}
timer &
pid=$!
echo $pid
sleep 5 # do something while timer runs
echo "done"
kill -9 $pid
Two things:
Don't use kill -9 to kill it. SIGKILL is uncatchable. It doesn't let the target process do any cleanup. Just do a plain kill to send a SIGTERM signal.
You can trap on SIGTERM. You could also trap on SIGINT to catch Ctrl-C. Or best, trap on EXIT to do cleanup no matter how the script is killed.
function timer () {
trap 'echo -e "\ntimer stopped"' EXIT
t0=$(date +%s)
while true ; do
t=$(date +%s)
echo -en "\r$(($t - $t0))"
done
}
timer &
pid=$!
echo "$pid"
sleep 5 # do something while timer runs
echo "done"
kill "$pid"
I'm running several background processes in my script
run_gui()
{
exec ... # the real commands here
}
The functions run_ai1(), run_ai2 are analogous.
Then I run the functions and do the needed piping
run_gui &
run_ai1 &
run_ai2 &
while true; do
while true; do
read -u $ai1_outfd line || echo "Nothing read"
if [[ $line ]]; then
: # processing
fi
done
sleep $turndelay
while true; do
read -u $ai2_outfd line || echo "nothing read"
if [[ $line ]]; then
: # processing
fi
done
sleep $turndelay
done
If any of those three processes exits, I want to check their exit codes and terminate the rest of the processes. For example, if run_ai2 exits with exit code 3, then I want to stop the processes run_ai1 and run_gui and exit the main script with exit code 1. The correct exitcodes for the different backgrounds processes may differ.
The problem is: how can I detect it? There's the command wait but I don't know in advance which script will finish first. I could run wait as a background process - but it's becoming even more clumsy.
Can you help me please?
The following script monitors test child processes (in the example, sleep+false and sleep+true) and reports their PID and exit code:
#!/bin/bash
set -m
trap myhandler CHLD
myhandler() {
echo sigchld received
cat /tmp/foo
}
( sleep 5; false; echo "exit p1=$?" ) > /tmp/foo &
p1=$!
echo "p1=$p1"
( sleep 3; true; echo "exit p2=$?" ) > /tmp/foo &
p2=$!
echo "p2=$p2"
pstree -p $$
wait
The result is:
p1=3197
p2=3198
prueba(3196)─┬─prueba(3197)───sleep(3199)
├─prueba(3198)───sleep(3201)
└─pstree(3200)
sigchld received
sigchld received
exit p2=0
sigchld received
exit p1=1
It could be interesting to use SIGUSR1 instead of SIGCHLD; see here for an example: https://stackoverflow.com/a/12751700/4886927.
Also, inside the trap handler, it is posible to verify which child is still alive. Something like:
myhandler() {
if kill -0 $p1; then
echo "child1 is alive"
fi
if kill -0 $p2; then
echo "child2 is alive"
fi
}
or kill both childs when one of them dies:
myhandler() {
if kill -0 $p1 && kill -0 $p2; then
echo "all childs alive"
else
kill -9 $p1 $p2
fi
}
I´ve asked Bash trap - exit only at the end of loop and the submitted solution works but while pressing CTRL-C the running command in the script (mp3convert with lame) will be interrupt and than the complete for loop will running to the end. Let me show you the simple script:
#!/bin/bash
mp3convert () { lame -V0 file.wav file.mp3 }
PreTrap() { QUIT=1 }
CleanUp() {
if [ ! -z $QUIT ]; then
rm -f $TMPFILE1
rm -f $TMPFILE2
echo "... done!" && exit
fi }
trap PreTrap SIGINT SIGTERM SIGTSTP
trap CleanUp EXIT
case $1 in
write)
while [ -n "$line" ]
do
mp3convert
[SOMEMOREMAGIC]
CleanUp
done
;;
QUIT=1
If I press CTRL-C while function mp3convert is running the lame command will be interrupt and then [SOMEMOREMAGIC] will execute before CleanUp is running. I don´t understand why the lame command will be interrupt and how I could avoid them.
Try to simplify the discussion above, I wrap up an easier understandable version of show-case script below. This script also HANDLES the "double control-C problem":
(Double control-C problem: If you hit control C twice, or three times, depending on how many wait $PID you used, those clean up can not be done properly.)
#!/bin/bash
mp3convert () {
echo "mp3convert..."; sleep 5; echo "mp3convert done..."
}
PreTrap() {
echo "in trap"
QUIT=1
echo "exiting trap..."
}
CleanUp() {
### Since 'wait $PID' can be interrupted by ^C, we need to protected it
### by the 'kill' loop ==> double/triple control-C problem.
while kill -0 $PID >& /dev/null; do wait $PID; echo "check again"; done
### This won't work (A simple wait $PID is vulnerable to double control C)
# wait $PID
if [ ! -z $QUIT ]; then
echo "clean up..."
exit
fi
}
trap PreTrap SIGINT SIGTERM SIGTSTP
#trap CleanUp EXIT
for loop in 1 2 3; do
(
echo "loop #$loop"
mp3convert
echo magic 1
echo magic 2
echo magic 3
) &
PID=$!
CleanUp
echo "done loop #$loop"
done
The kill -0 trick can be found in a comment of this link
When you hit Ctrl-C in a terminal, SIGINT gets sent to all processes in the foreground process group of that terminal, as described in this Stack Exchange "Unix & Linux" answer: How Ctrl C works. (The other answers in that thread are well worth reading, too). And that's why your mp3convert function gets interrupted even though you have set a SIGINT trap.
But you can get around that by running the mp3convert function in the background, as mattias mentioned. Here's a variation of your script that demonstrates the technique.
#!/usr/bin/env bash
myfunc()
{
echo -n "Starting $1 :"
for i in {1..7}
do
echo -n " $i"
sleep 1
done
echo ". Finished $1"
}
PreTrap() { QUIT=1; echo -n " in trap "; }
CleanUp() {
#Don't start cleanup until current run of myfunc is completed.
wait $pid
[[ -n $QUIT ]] &&
{
QUIT=''
echo "Cleaning up"
sleep 1
echo "... done!" && exit
}
}
trap PreTrap SIGINT SIGTERM SIGTSTP
trap CleanUp EXIT
for i in {a..e}
do
#Run myfunc in background but wait until it completes.
myfunc "$i" &
pid=$!
wait $pid
CleanUp
done
QUIT=1
When you hit Ctrl-C while myfunc is in the middle of a run, PreTrap prints its message and sets the QUIT flag, but myfunc continues running and CleanUp doesn't commence until the current myfunc run has finished.
Note that my version of CleanUp resets the QUIT flag. This prevents CleanUp from running twice.
This version removes the CleanUp call from the main loop and puts it inside the PreTrap function. It uses wait with no ID argument in PreTrap, which means we don't need to bother saving the PID of each child process. This should be ok since if we're in the trap we do want to wait for all child processes to complete before proceeding.
#!/bin/bash
# Yet another Trap demo...
myfunc()
{
echo -n "Starting $1 :"
for i in {1..5}
do
echo -n " $i"
sleep 1
done
echo ". Finished $1"
}
PreTrap() { echo -n " in trap "; wait; CleanUp; }
CleanUp() {
[[ -n $CLEAN ]] && { echo bye; exit; }
echo "Cleaning up"
sleep 1
echo "... done!"
CLEAN=1
exit
}
trap PreTrap SIGINT SIGTERM SIGTSTP
trap "echo exittrap; CleanUp" EXIT
for i in {a..c}
do
#Run myfunc in background but wait until it completes.
myfunc "$i" & wait $!
done
We don't really need to do myfunc "$i" & wait $! in this script, it could be simplified even further to myfunc "$i" & wait. But generally it's better to wait for a specific PID just in case there's some other process running in the background that we don't want to wait for.
Note that pressing Ctrl-C while CleanUp itself is running will interrupt the current foreground process (probably sleep in this demo).
One way of doing this would be to simply disable the interrupt until your program is done.
Some pseudo code follows:
#!/bin/bash
# First, store your stty settings and disable the interrupt
STTY=$(stty -g)
stty intr undef
#run your program here
runMp3Convert()
#restore stty settings
stty ${STTY}
# eof
Another idea would be to run your bash script in the background (if possible).
mp3convert.sh &
or even,
nohup mp3convert.sh &
I am a bit confused about process control within a bash script.
What I want, is to run specific functions/routines and at 10:00 stop them and then run some other functions. When the "other functions" end, I want the first functions continue exactly from the spot they were when they were stopped.
For example, suppose I run main.sh at 09:50. I want the function_one to stop when time is 10:00, run function_two and then continue with function_one at the exact state it was. Needless to say, that function_one could be quite complex, and have background child processes itself.
$ cat main.sh
#!/bin/bash
source functions.sh
function_one &
echo $! > function_one_running_in_background_PID.txt
while true; do
sleep 10s
if [[ $(date +%H%M) = 1000 ]]; then
kill -SIGSTOP $(<function_one_running_in_background_PID.txt)
echo Time is 10
function_two
kill -SIGCONT $(<function_one_running_in_background_PID.txt)
fi
done
$ cat functions.sh
#!/bin/bash
function function_one {
for i in {1..100000}; do echo 1st function: $i; sleep 1; done
}
function function_two {
for i in {1..100}; do echo 2nd function: $i; sleep 1; done
}
Could you please tell me if the above is possible? "Maybe" I am missing something in bash. Maybe there is a better way to do it instead of what I have thought.
Thank you all!
I think this can work. Some notes:
Even if you stop a process, its child processes will continue to run happily. If you want to stop those as well, then you need to trap STOP and CONT signals in the parent to stop and resume the children.
Instead saving the PIDs in files, why not save in variables?
You can shorten kill -SIGSTOP as kill -STOP, same for CONT
Here's a sample demo code to play with:
#!/bin/sh -e
f1() { for i in {1..1000}; do echo f1 $i; sleep 1; done; }
f2() { for i in {1..1000}; do echo f2 $i; sleep 1; done; }
f1 &
PID1=$!
f2 &
PID2=$!
kill -STOP $PID2
turn=1
while :; do
if test $turn = 1; then
kill -STOP $PID1
kill -CONT $PID2
turn=2
elif test $turn = 2; then
kill -STOP $PID2
kill -CONT $PID1
turn=1
fi
sleep 5
done
Rather than run a busy loop that constantly checks if it is 10:00, just figure out how many seconds until 10:00 and sleep that long before stopping the first process. There's no need for a temporary file, as you can just store the process ID in another parameter until needed.
t=$(( $(date +%s --date 1000) - $(date +%s) ))
function_one & f1_pid=$!
sleep $t
kill -STOP $f1_pid
function_two
kill -CONT $f1_pid
If function_one forks a child of its own, you will have to make sure that it is written in such a way to stop them when it is stopped itself.
To catch all the children I suppose they will belong to the same process-group, initiated by the program that launches them. Killing the group will kill all children, grand children etc.
It could be as simple as this:
#!/bin/bash
#set the 'at' timer on 22:00 to give self a signal (see 'man at' )
at 22:00 <<<"/usr/bin/kill -SIGCONT $$"
#gentlemen...start your engines..ehrm..programs!
"/path/firstset/member1.sh" &
"/path/firstset/member2.sh" &
"/path/firstset/member3.sh" &
"/path/firstset/member4.sh" &
while :
do
kill -SIGSTOP "$$" #<--now we halt this script.
# wait for SIGCONT given by 'at'
# Very nicely scheduled on 22:00
#when waking up:
PLIST=$(pgrep -g "$$") # list PIDs of group (= self + subtree)
PLIST=${PLIST/"$$"/} # but not self, please
kill -SIGSTOP $PLIST #OK it is 22:00, halt the old bunch
"/path/secondset/member1.sh" & # start the new bunch
"/path/secondset/member2.sh" &
"/path/secondset/member3.sh" &
"/path/secondset/member4.sh" &
wait # wait till they are finished
kill -SIGCONT $PLIST #old bunch can continue now,
done #until next scheduled event.
Thank you all for your immediate answers and I apologize for the delay. I had a system disk drive failure (!!)
Janos, what happens if f1 has background processes in it? How could I stop all of them?
#!/bin/sh -e
f1() {
f3 &
f4 &
}
f2() { for i in {1..1000}; do echo f2 $i; sleep 1; done; }
f3() { for i in {1..1000}; do echo f3 $i; sleep 1; done; }
f4() { for i in {1..1000}; do echo f4 $i; sleep 1; done; }
f1 &
PID1=$!
f2 &
PID2=$!
kill -STOP $PID2
while :; do
if [[ $(date +%S) = *0 ]]; then
kill -STOP $PID1
kill -CONT $PID2
elif [[ $(date +%S) = *5 ]]; then
kill -STOP $PID2
kill -CONT $PID1
fi
sleep 1
done
Greg's Wiki has this very simple example of how to keep a server running s.t. if it exits, it is instantly restarted:
#!/bin/sh
while :; do
/my/game/server -foo -bar -baz >> /var/log/mygameserver 2>&1
done
But how about where you want to keep N servers running, s.t. if one fails, all should be restarted? http://wiki.bash-hackers.org/scripting/bashchanges says bash 4.3 will let me do
while :; do
server1 & p1=$!
server2 & p2=$!
wait -n $p1 $p2 # wait until at least one exits
kill $p1 $p2
done
but 4.3 is still in alpha, is there a way to do this with older systems?
Here's the method I came up with, based on Greg's Wiki and some help from #bash on irc.freenode.net:
#!/bin/bash
trap 'rm -f manager; kill 0' EXIT
mkfifo manager
declare -A pids
restart () {
# assuming your servers/daemons are programs "a" and "b"
[[ -n ${pids[a]} ]] && kill "${pids[a]}"
[[ -n ${pids[b]} ]] && kill "${pids[b]}"
run_and_tell manager a & pids[a]=$!
run_and_tell manager b & pids[b]=$!
}
restart
while :; do
read < manager
restart
done
and run_and_tell:
#!/bin/bash
trap 'kill $pid' EXIT
manager=$1
prog=$2
$prog & pid=$!
wait $pid
echo >"$manager"
Not as nice as the bash 4.3 version, but it seems to work (e.g. testing with "sleep 9999" in run_and_tell). One annoyance is that I have to trap 'kill $pid' EXIT in the runner, and it seems I have to do the same in $prog, to ensure it's killed when its parent is killed.
Here's an alternative version that avoids having to trap, by putting run_and_tell in its own process group:
#!/bin/bash
# The trap now needs to kill all created process groups:
trap 'rm -f manager; kill 0; kill ${pids[a]} ${pids[b]}' EXIT
mkfifo manager
declare -A pids
restart () {
# assuming servers/daemons are programs "a" and "b":
[[ -n ${pids[a]} ]] && kill -TERM -"${pids[a]}"
[[ -n ${pids[b]} ]] && kill -TERM -"${pids[b]}"
setsid ./run_and_tell manager a & pids[a]=$!
setsid ./run_and_tell manager b & pids[b]=$!
}
restart
while :; do
read < manager
restart
done
and run_and_tell becomes just:
#!/bin/bash
manager=$1
prog=$2
$prog
echo >"$manager"
The simplest way is to check them manually for every interval:
#!/bin/bash
function check_if_all_active {
local p
for p in "$#"; do
kill -s 0 "$p" &>/dev/null || return 1
done
return 0
}
while :; do
pids=()
server1 & pids+=("$!")
server2 & pids+=("$!")
while check_if_all_active "${pids[#]}"; do
sleep 1s ## Can be longer.
done
kill -s SIGTERM "${pids[#]}" &>/dev/null
done
You can also consider other signals to stop your processes like SIGHUP or SIGABRT.