Differences between revisions 5 and 6
Revision 5 as of 2023-11-22 12:00:18
Size: 2429
Editor: geirha
Comment: Include example using a procsub
Revision 6 as of 2023-11-22 12:02:49
Size: 2441
Editor: geirha
Comment: wait on procsub requires bash 4.4+
Deletions are marked like this. Additions are marked like this.
Line 44: Line 44:
wait "$!" # wait for inotifywait to reap its exit status. wait "$!" # wait for inotifywait to reap its exit status. (bash 4.4+)

I have a pipeline where a long-running command feeds into a filter. If the filter finds "foo", I want the long-running command to die.

In general this is not possible, because sibling processes (two children of the same parent) do not have any knowledge of each other. But consider the following example and answers:

We'll assume the following pipeline, which uses Linux's inotifywait program:

   1 inotifywait -m --format '%e %f' uploads |
   2 while read -r events file; do
   3   if [[ $events = *MOVED_TO* ]]; then
   4     : do something special
   5     if [[ $file = *abort* ]]; then
   6       : special sentinel file found
   7       : want to kill the inotifywait process
   8     fi
   9   fi
  10 done

In this example, the user wants to stop the inotifywait program if a rename is performed with the resulting filename containing the string "abort". What are our options here?

  • Simply exit from the while loop, and do not explicitly try to do anything to inotifywait. The shell process containing the while loop will terminate, and the inotifywait process will continue running. As soon as inotifywait tries to write another line of output to the pipe (which has been closed), it will receive a SIGPIPE signal, which will terminate it.

  • Find an option within the feeder process to exit when something bad happens, instead of relying on an external filter to do it for you. (This will only be possible with certain feeder programs, but it's worth taking the time to read the documentation and see whether it's possible.)

  • Replace the anonymous pipeline with a FIFO and run the feeder as a background process explicitly. Save its PID. Wait for the filter to exit, and when it does so, kill the long-running feeder process. Thus:

  •    1 filter() {
       2   while read -r events file; do
       3     : ...
       4   done
       5 }
       6 
       7 fifo="${TMPDIR:-${XDG_RUNTIME_DIR:-/tmp}}//fifo.$$"
       8 trap 'rm -rf -- "$fifo"' EXIT
       9 mkfifo -- "$fifo" || exit
      10 
      11 inotifywait -m --format '%e %f' uploads > "$fifo" & pid=$!
      12 filter < "$fifo"
      13 kill -- "$pid"
      14 wait
    
  • A process substitution can be used instead of a fifo

       1 { filter ; } < <( inotifywait -m --format '%e %f' uploads )
       2 kill "$!"  # kill inotifywait as soon as filter returns
       3 wait "$!"  # wait for inotifywait to reap its exit status. (bash 4.4+)
    

BashFAQ/117 (last edited 2023-11-22 12:02:49 by geirha)