shell快速搞定多进程通信的小技巧

383 阅读2分钟

image.png

背景问题描述

我有一个流水线,其中长时间运行的命令输入到一个过滤器中。如果过滤器发现了"foo",我希望长时间运行的命令停止执行。

一般情况下,这是不可能的,因为兄弟进程(同一个父进程的两个子进程)之间没有彼此的信息。但考虑以下示例和答案:

我们假设以下使用Linux的inotifywait程序的流水线:

 inotifywait -m --format '%e %f' uploads |
 while read -r events file; do
   if [[ $events = *MOVED_TO* ]]; then
     : do something special
     if [[ $file = *abort* ]]; then
       : special sentinel file found
       : want to kill the inotifywait process
     fi
   fi
 done

在这个示例中,用户希望在执行重命名操作时,如果结果文件名包含字符串"abort",停止inotifywait程序。我们在这里有哪些选择呢?

  1. 简单地从while循环中退出,并且不对inotifywait做任何显式操作。包含while循环的shell进程将终止,而inotifywait进程将继续运行。当inotifywait尝试向管道写入另一行输出时(已关闭),它将接收到一个SIGPIPE信号,从而终止它。
  2. 在原始生成数据的进程中查找退出的选项,当发生异常情况时,而不是依赖外部过滤器来完成这个任务。(这只适用于某些生成数据的程序,但值得花时间阅读文档并确定是否可行。)
  3. 使用FIFO替换匿名流水线,并将原始生成数据的进程作为后台进程显式运行。保存其PID。等待过滤器退出,一旦过滤器退出,就杀死长时间运行的生成数据进程。实现如下:
filter() { 
  while read -r events file; do 
      : ... 
  done 
} 

fifo="${TMPDIR:-${XDG_RUNTIME_DIR:-/tmp}}//fifo.$$"  
trap 'rm -rf -- "$fifo"' EXIT
mkfifo -- "$fifo" || exit

inotifywait -m --format '%e %f' uploads > "$fifo" & 
pid=$! 

filter < "$fifo"

kill -- "$pid"  
wait

知识点

inotifywait 是 Linux 内核 Inotify 事件监测机制的 Shell 前端,用于监视文件系统事件。

它主要用于:

  1. 监测文件和目录的修改:
  • 文件是否被创建、修改、删除
  • 目录是否有文件被创建、修改、删除
  1. 自动执行脚本:

每当监听的文件或目录有变化时,可以自动运行指定的 Shell 脚本。

如你提供的例子:

inotifywait -m --format '%e %f' uploads > "$fifo" & 

它监听 uploads 目录,如果 uploads 目录下有文件被创建(create)或修改(modify),就会输出事件和文件名到"$fifo" 文件中。

这样就可以基于 inotifywait 来建立自动执行脚本的功能。一般使用它的场景有:

  • 自动编译程序:监视源码变化自动重新编译
  • 自动部署程序:监视代码提交自动部署最新版本
  • 自动处理文件:监视文件变化自动运行处理脚本

inotifywait 可以让 Shell 脚本具备“事件驱动”的能力,比 periodically cron 更高效。