Konstantinos
Konstantinos

Reputation: 4376

Process control in bash scripts

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!

Upvotes: 2

Views: 983

Answers (4)

Konstantinos
Konstantinos

Reputation: 4376

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

Upvotes: 0

janos
janos

Reputation: 124704

I think this can work. Some notes:

  1. 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.

  2. Instead saving the PIDs in files, why not save in variables?

  3. 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

Upvotes: 1

thom
thom

Reputation: 2332

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.

Upvotes: 1

chepner
chepner

Reputation: 531693

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.

Upvotes: 1

Related Questions