緣起
前一篇用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>&- & |
代替原來的命令就行了。