用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>&- &

代替原來的命令就行了。