用Makefile搭建博客-2

缘起

前一篇用Makefile搭建博客说到我用inotifywait监控目录下文件的写操作来更新目录的修改时间,以后只要把make inotify放到启动脚本中就行了。但当时就发现了一个问题,一直拖到今天才解决掉。

GoboLinux Scripts包中的ColorMake

先从/ColorMake/说起。/GNU Make/的颜色是很单调的,我一般用GoboLinux的包Scripts里的ColorMake来给make上色。而这个ColorMake实际上是写了一个mtail的配置文件ColorMake.mtailrc,把make的输出管道给mtail来上色。可以参看。我之前则是用的http://bre.klaki.net/programs/colormake/里的colormake.pl脚本来上色的。

我把ColorMake.mtailrc保存为 ~/bin/ColorMake.mtailrc ,另外写了个 wrapper,保存到 ~/bin/mk ,内容如下:

#!/bin/sh
/usr/bin/make "$@" 2>&1 | mtail -q --config ~/bin/ColorMake.mtailrc

意思就是把mk的参数全部传递给makemake的 stdout stderr 全部管道给mtail来上色。

另外, ~/bin 在我的环境变量PATH中。

产生问题的命令

先来看我的 [[/Makefile][Makefile]],只要看 inotify 伪目标,其他的可以忽略掉。之前 inotify 的规则是 inotifywait -e modify -m -r . --format %w | xargs -I % sh -c "touch `dirname %`" &

make inotify 运行得非常正常,inotifyxargs在后台执行;但如果执行mk inotify,问题就来了,终端会被占着,无法再执行其他命令了。

分析

make inotify

先来看执行make inotify会发生什么,不妨假设交互用的shell是Zsh,这里用Bash效果也是一样的。

-zsh进程产生一个make进程 -make进程执行重建 inotify 的规则,即产生一个进程执行inotifywait -e modify -m -r . --format %w | xargs -I % sh -c "touch \`dirname %\`" &

不妨用 /Lisp/ 来表示进程树,那么这些进程的关系如下: (zsh (make (inotifywait) (xargs))) 。 一对圆括号代表进程,圆括号第一个元素是进程名,其余元素代表子进程

接着,

-make退出,因为规则执行完了 -zsh检测到它的子进程make退出,又可以执行其他命令了

mk inotify

-zsh进程产生mk,其实是用/bin/sh解释mk,这里就简写成mk -mk产生makemtail,其中管道的一端是make的fd1、fd2,另一端是mtail的fd0 -make产生inotifywaitxargs

第三步中,make的文件描述符被inotifywaitxargs继承, 由于inotifywaitxargs用另一根管道而把 fd1 关闭了, 所以现在原管道的两端分别是:

-make的fd1、fd2;inotifywait的fd2;xargs的fd1、fd2 -mtail的fd0

现在的进程树是: (zsh (mk (make (inotifywait) (xargs)) (mtail)))

接着,

  • make退出,因为规则执行完了
  • 因为管道的写端描述符没有全部关闭,mtail不会读到EOF退出,而是等待管道读端的数据
  • mk也不会退出,因为它的某个子进程mtail没有退出
  • zsh未检测到mk的退出,所以终端被占用了,没法执行其他命令

解决方案

由前面的分析可以看出,只要让mtail退出,那么mk会跟着退出,终端就不会被占用了。 而要让mtail退出,就要让它读到 eof 退出,我们只要让make产生进程时不要把管道的写端描述符 传递给inotifywaitxargs,但是这个据我所知是做不到的。 但我们可以让inotifywaitxargs立刻把相应写端描述符关闭, 这个很简单,用

1
inotifywait -e modify -m -r . --format %w 2>&- | xargs -I % sh -c "touch \`dirname %\`" >&- 2>&- &

代替原来的命令就行了。