3

I have a asynchronous while loop that restarts processes until some condition is met. This look something like this:

(while [ "$check_condition" -eq 1 ]; do
    ./async_command
done) &

... # do some stuff

check_condition=0
pkill -9 async_command

As you can see, I change check_condition to 0 and kill the async process that is still running. The problem is, that the while loop still fires and the process is restarted.

I'm guessing that the loop only registers the value of check_condition once it starts and doesn't notice any changes.

How can I fix this?

N. Krh
  • 33
  • 6

2 Answers2

3

You need kill $! to kill the last running process at background. see In Bash scripting, what's the meaning of " $! "?

if there are other commands you are running in the background, you can save the PID of that in a variable from $!, later use that Id to kill that process anywhere you needed.

( while ... done ) & pid=$!

then

kill $pid

or use jobs to determine which background process is running there and kill it with their Id.

kill %Id

see also How to terminate a background process?

αғsнιη
  • 40,939
  • 15
  • 71
  • 114
  • Thanks for the answer! My problem is not the terminating of this single process, though. It terminates fine with `pkill`, but since its executed in the while loop, it gets restarted. Also, I am calling some other commands between starting the process and killing it, so `$!` wouldn't return the right value. – N. Krh Mar 18 '21 at 09:26
  • 1
    `$!` is not the Id for last process, its the Id for the last ***background running*** process, if there is no other background running process after `while... &`, you can still use that (or see [@Kusalananda's suggestion](https://unix.stackexchange.com/questions/639852/stopping-an-asynchronous-while-loop-in-a-shell-script#comment1199268_639854) below) , else second option (using `jobs`) is your best choice. greping to find the relevant id based on name of the process then kill it as said. – αғsнιη Mar 18 '21 at 09:29
  • 2
    @N.Krh Save `$!` in a variable: `( while ... done ) & pid=$!`. Then `kill "$pid"`. Also, `$!` won't be overwritten unless you start other _asynchronous_ jobs. – Kusalananda Mar 18 '21 at 09:30
  • I do start multiple of those while loops, so I have multiple pids that have to be killed. Right now, I'm storing the pids in an array and loop the array with a kill command later. This does work, but unfortunately this also terminates my whole script. Any idea why that happens? – N. Krh Mar 18 '21 at 09:58
  • @N.Krh it should not kill the script itself, how do you test/validate that if your script getting killed? how do you use kill? – αғsнιη Mar 18 '21 at 10:08
  • 1
    I made it work using `kill -9` and giving all pids on a single line. Perhaps something was wrong with my loop. Thanks for the help! – N. Krh Mar 18 '21 at 10:44
2

coproc for simulating named loops

The closest to named loops bash has, might be coproc:

coproc loop1 { while ((1)) ; do echo a >> aout ; sleep 1 ; done ;} 
echo PID of loop1 $loop1_PID
coproc loop2 { while ((1)) ; do echo b >> bout ; sleep 1 ; done ;} 
echo PID of loop2 $loop2_PID
coproc loop3 { while ((1)) ; do echo c >> cout ; sleep 1 ; done ;}
echo PID of loop3 $loop3_PID

sleep 2
kill $loop1_PID
sleep 2
kill $loop2_PID
sleep 2
kill $loop3_PID

I.e.

coproc NAME simple_command 
#or
coproc NAME { series ; of ; commands ;}

will store the PID in NAME_PID and you may kill the process lateron using this.


Alternative: dummy files

While using PIDs as suggested by @αғsнιη is the most correct way, here another idea by just creating a dummy file:

 #!/bin/bash
 dummy="$(mktemp -u -p .)"
 ( while [ ! -e "$dummy" ] ; do ./async_command ; done ) &
 ...
 #condition met
 touch "$dummy"
 ...

You could use this file also as indicator for meeting the condition or add a trap if you want it cleaned up. Again: control via PID should be preferred, but you could abuse it to "name" your loops and stop the correct ones.

FelixJN
  • 12,616
  • 2
  • 27
  • 48