0

I start a python script and then after some time i want to kill it. but before killing it I want to start another copy of this same script and then kill the previous one after starting new one. I want to do this in loop.

here is my code , i just need a clean way to kill scripts.I do not want to use timeouts.

#!/bin/bash 
while true
do
    echo "starting FIRST Consumer.py : $(date +"%T")"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &

    sleep 20
    echo "starting SECOND Consumer.py : $(date +"%T")"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    # Here I want to kill FIRST Consumer.py
    sleep 20
    # Here I want to kill SECOND Consumer.py

done
irum zahra
  • 417
  • 1
  • 8
  • 17
  • Any reason you don't want to use timeout 20 python3 ... ? http://man7.org/linux/man-pages/man1/timeout.1.html – Olf Jan 29 '19 at 13:19
  • yes i was using timeouts but sometimes it doesnt kill my scripts. but starts new ones. after some time i had 8 scripts running. – irum zahra Jan 29 '19 at 13:21
  • Are you sure about the comments in your code sample? (did you invert SECOND and FIRST ?) – Olf Jan 29 '19 at 13:24
  • i have edited the comments :p – irum zahra Jan 29 '19 at 13:26
  • sudo killall -9 Consumer.py or whatever matches your file names. – Amresh Giri Jan 29 '19 at 13:28
  • 1
    Clean and kill are kind of conflicting requirements. Where kill means "stop at any cost", and clean meaning "exit gracefully" . If you're program isn't exiting after a `SIGTERM` signal then it is misbehaving. Fix your python script before resorting to using `SIGKILL`. Downsides to using `SIGKILL` include losing state if the process isn't able to save state about what has been consumed. – Dunes Jan 29 '19 at 13:45

4 Answers4

2

Each time a background process is started, bash maintains a jobs list, with ids starting from 1.

The jobs builtin lists the background jobs.

in you situation, you would add the kill %1 to kill the first background job ever started, and then kill %2

== EDITED == Your script becomes:

#!/bin/bash 
while : ; do
   echo "starting FIRST Consumer.py : $(date +"%T")"
   python3 /home/irum/Desktop/Marketsyc/Consumer.py &

   sleep 20
   echo "starting SECOND Consumer.py : $(date +"%T")"
   python3 /home/irum/Desktop/Marketsyc/Consumer.py &

   kill %1

   sleep 20

   kill %2

   wait
 done
Jay jargot
  • 2,745
  • 1
  • 11
  • 14
  • The job ids always increase. `%1` and `%2` are only correct for the first iteration. In the second iteration you would have to use `%3` and `%4`. You can access the PID (not job id) of the last job with `$!`. – Socowi Jan 29 '19 at 13:41
  • @Socowi: as the loop is ending without any background jobs, the ids are always 1 and 2. This is confirmed with my tests. – Jay jargot Jan 29 '19 at 13:47
  • Are you sure? I tested my claim in bash 4.4.19(1) and 5.0.0(1) on two different systems. For me, the following script quickly prints numbers > 2. `while :; do sleep 10 & sleep 20 & kill %1; kill %2; jobs | wc -l; done`. – Socowi Jan 29 '19 at 13:54
  • Maybe you are right nevertheless. `sleep 1 & kill %1; sleep 2 & jobs` creates the job ids %1 and %2, but `sleep 1 & kill %1; sleep 0.5; sleep 2 & jobs` uses the job id %1 twice. Seems like `kill` is asynchronous and we need a small pause between killing an old job and starting a new one such that the old job id is freed before the new job is started. – Socowi Jan 29 '19 at 14:03
  • This small quirks are the reason I prefer to work with PIDs in scripts. Interactive job management is another beast. – Poshi Jan 29 '19 at 15:06
  • Kill just sends the signal. The process might not terminate immediately because the it hasn't finished yet or the kernel hasn't finished cleaning it up yet. The cleaning of bash's jobs table happens asynchronously. You can force it to synchronous by waiting on the job. Eg `kill %1; wait %1; sleep 101 &` gives the new sleep a job id of 1. HOWEVER, that's only true if there was no second job. Bash always gives new jobs a number 1 greater than the highest id of all active jobs. That is, given jobs 1 and 3, a new process will have a job id of 4 and not 2. – Dunes Jan 29 '19 at 15:09
  • @Dunes: the `wait` is a good idea. If I group all these remarks here, the best, when someone want to use the jobs, would be the @Socowi 's answer using `kill %-` and `kill %+`. The answer had been edited to include `wait`. – Jay jargot Jan 30 '19 at 08:19
  • @Jayjargot Wow, using `wait` is clever. Never would have thought of that. I think with `wait` your answer is better than mine since you don't risk killing the wrong job if a job terminates before you kill it manually. It's even better than using PIDs. @Poshi With `kill $pid` there is a chance to kill an external process by accident. When the job with $pid exits before we kill it manually its PID might get re-used by a new process if a [pid wrap around](https://stackoverflow.com/a/11323428/6770384) happens. – Socowi Jan 30 '19 at 09:01
  • @Socowi you are right, the PID can be reused. I assumed that the jobs started had to be killed. If they can finish before the killing order... we can get in a trouble. Either case, the best approach with `bash` job management is to use relative jobs and don't rely on havint the first job numbered as %1. What would happen if we already had a couple jobs running in the background? It happens a lot to me due to opening text editors in the background. – Poshi Jan 30 '19 at 10:01
2

You can get the PID of the first process to kill it afterwards:

#!/bin/bash 

while true
do
    echo "starting FIRST Consumer.py : $(date +"%T")"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    pid=$!
    sleep 20

    echo "starting SECOND Consumer.py : $(date +"%T")"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    new_pid=$!

    # Here I want to kill FIRST Consumer.py
    kill "$pid"
    sleep 20

    # Here I want to kill SECOND Consumer.py
    kill "$new_pid"
done
Poshi
  • 5,332
  • 3
  • 15
  • 32
  • this answer worked for me but when i added -9 to kill command `kill -9 "$pid"` – irum zahra Feb 06 '19 at 12:12
  • This detail depends on what are you trying to kill. By default, `kill` sends a `SIGTERM` signal (15), and this signal can be catched and processed. It is supposed to give the application a chance to write down it's status and exit grecefully. But if it does not exit, you can always use the `SIGKILL` (9), which cannot be trapped and forces the system to kill the application. – Poshi Feb 06 '19 at 13:50
2

In bash there are two special background jobs: + and -. You can see them in the output of the jobs command:

$ sleep 10 &
$ sleep 20 &
$ sleep 30 &
$ jobs
[1]   Running                 sleep 10 &
[2]-  Running                 sleep 20 &
[3]+  Running                 sleep 30 &

+ is the most recent running job. - is the most recent job before +. Both will update if you kill a job or start a new one. Therefore you could complete your script as follows:

#!/bin/bash 
while true; do
    echo "starting FIRST Consumer.py : $(date +%T)"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    sleep 20
    echo "starting SECOND Consumer.py : $(date +%T)"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    kill %- # kill FIRST Consumer.py
    sleep 20
    kill %+ # kill SECOND Consumer.py
done

This only works reliably if Consumer.py always runs longer than 20 seconds. If Consumer.py could terminate before the 20 second are over, then we may kill the wrong job. To work around this issue, you can use (python3 .../Consumer.py; sleep inf) & instead of python3 .../Consumer.py &. The sleep inf will stop the job from terminating on its own.

Socowi
  • 25,550
  • 3
  • 32
  • 54
0

I would do this

    #!/bin/bash 
function check_pid() {
    if /bin/ps -p $1 > /dev/null
    then
        kill $1
    else
        echo "not running"
    fi
}

while true
do
    echo "starting FIRST Consumer.py : $(date +"%T")"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    pid1=$!
    sleep 20
    echo "starting SECOND Consumer.py : $(date +"%T")"
    python3 /home/irum/Desktop/Marketsyc/Consumer.py &
    pid2=$!
    # Here I want to kill FIRST Consumer.py
    check_pid $pid1
    sleep 20
    # Here I want to kill SECOND Consumer.py
    check_pid $pid2
done

In this way, the process will be killed only if it is still running.