缘起
前一篇用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
的参数全部传递给make
,make
的
stdout stderr 全部管道给mtail
来上色。
另外, ~/bin 在我的环境变量PATH
中。
产生问题的命令
先来看我的 [[/Makefile][Makefile]],只要看 inotify 伪目标,其他的可以忽略掉。之前 inotify 的规则是 inotifywait -e modify -m -r . --format %w | xargs -I % sh -c "touch `dirname %`" &
make inotify
运行得非常正常,inotify
和xargs
在后台执行;但如果执行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
产生make
和mtail
,其中管道的一端是make
的fd1、fd2,另一端是mtail
的fd0
-make
产生inotifywait
和xargs
第三步中,make
的文件描述符被inotifywait
和xargs
继承,
由于inotifywait
和xargs
用另一根管道而把 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
产生进程时不要把管道的写端描述符
传递给inotifywait
和xargs
,但是这个据我所知是做不到的。
但我们可以让inotifywait
和xargs
立刻把相应写端描述符关闭,
这个很简单,用
1 | inotifywait -e modify -m -r . --format %w 2>&- | xargs -I % sh -c "touch \`dirname %\`" >&- 2>&- & |
代替原来的命令就行了。