Search code examples
bashloopskillforeground

Parallel processes in bash, kill with Ctrl C


Just made my first bash script. It spawns a http-server process in parallel for each folder in a certain directory.

Here is what I have:

PORT=8001
cd websitesfolder
for d in */ ; do
  http-server $d -p $PORT &
  let "PORT++" 
done

This does spawn the http-servers, but when I exit with Ctrl C, the processes are not killed.

I implemented this without a loop first, and adding && fg I was able to kill the processes using Ctrl+C

http-server dir1 -p 8001 & http-server dir2 -p 8002 && fg

Is something similar possible with my looped solution?

edit: system is macOS


Solution

  • A simple approach -- albeit not completely reliable, should a process die and have something else be assigned its PID number -- is simply to maintain an array of PIDs:

    #!/usr/bin/env bash
    
    pids=( )    
    port=8001
    
    graceful_shutdown() {
      (( ${#pids[@]} )) && kill "${pids[@]}"
    }
    
    cd websitesfolder || exit
    for d in */ ; do
      http-server "$d" -p "$port" & pids+=( "$!" )
      (( ++port ))
    done
    
    trap graceful_shutdown EXIT
    for pid in "${pids[@]}"; do
      wait "$pid" || (( retval |= $? ))
    done
    exit "$retval"
    

    A better approach is to have a lockfile per port, or to use fuser -k to kill the processes that actually hold the ports open directly, rather than assuming that you know the PIDs:

    #!/usr/bin/env bash
    
    graceful_shutdown() {
      fuser -k "$lockdir"/*
    }
    
    lockdir=/var/run/mydir # this needs to exist
    port=8001
    pids=( )
    
    cd websitesfolder || exit
    
    for d in */; do
      (exec 3>"$lockdir/$port" &&
       flock -x 3 &&
       exec http-server "$d" -p "$port") & pids+=( "$!" )
    done
    
    trap graceful_shutdown EXIT
    for pid in "${pids[@]}"; do
      wait "$pid" || (( retval |= $? ))
    done
    exit "$retval"