我的外部記憶區

2006年5月8日星期一

Unix痛恨者手冊2:第七章:Find

Unix最為恐怖的是,不管你被它開過多少次瓢,你總是沒法失去知覺。它就這麼開來開去,沒完沒了。

——Patrick Sobalvarro

在一個龐大的文件系統中遺失個把文件是常有的事(想像一下大海撈針)。現在由於更大更便宜的磁盤的出現,PC和Apple用戶也遇到了這樣的問題。為了解決這個問題,系統往往提供一個搜索程序,根據各種條件(比如文件名稱,類型,創建時間等等)進行文件搜索。Apple Macintosh和微軟Windows都提供強大、方便、穩定的文件搜索程序。這些搜索程序的設計中考慮到了用戶習慣和現代網絡。Unix的搜索程序find考慮的則不是用戶,而是cpio,一個Unix備份工具。Find沒能預見到網絡的存在和文件系統的新功能(如符號鏈接),即使是經歷了反覆修改,它還是無法很好工作。於是,儘管它對於遺失文件的用戶意義重大,find還是不能穩定、正常的工作。

Unix的作者們努力是find跟上系統其他部分的發展,但這並不容易。今天的find有各種特殊的選項用於處理NFS文件系統,符號鏈接,執行程序,交互式地執行程序,甚至直接使用cpio或cpio-c格式對找到的文件進行歸檔。Sun公司修改了find,添加了一個後台程序建立系統上每個文件的索引數據庫,由於一些奇怪的理由,當你不加任何參數執行"find filename"時,這個數據庫被用於進行搜索,(夠安全 的,是吧?) 即使有個這麼多修修補補,find還是不能正常工作。

例如,csh見到符號鏈接會順著走下去,但find不會:csh是伯克利(符號鏈接的發源地)的傢伙們寫的,可是find是從AT&T的原始時代開始就有了。就這樣,東西方的文化差異激烈地碰撞了,造成了巨大的混亂:

日期: Thu, 28 Jun 1990 18:14 EDT 發信人: pgs@crl.dec.com 主題: more things to hate about Unix (更多恨的理由,就在Unix) 收信人: UNIX-HATERS 這個是我的最愛。我在一個目錄下工作,想用find去找另一個目錄裡的文件,我是這麼做的: po> pwd /ath/u1/pgs po> find ~halstead -name "*.trace" -print po> 看來沒有找到。不過別忙,看看這個: po> cd ~halsead po> find . -name "*.trace" -print ../learnX/fib-3.trace ../learnX/p20xp20.trace ../learnX/fib-3i.trace ../learnX/fib-5.trace ../learnX/p10xp10.trace po> 嘿!文件就在那裡呀!下次如果你想找一個文件,記住隨機到各個目錄下轉轉,說不定你要的文件就藏在那裡呢。Unix這個廢物。

可憐的Halstead同志的/etc/passwd記錄一定是使用了符號鏈接去指向了真正的目錄,所以有的命令工作,有的不工作。

為什麼不改改find,也讓它順著符號鏈接呢?這是因為任何一個指向高一級目錄的符號鏈接都會把find引入死循環。要處理這種情況需要精心的設計和小心的實現,以保證系統不會重複搜索同一個目錄。Unix採用了最簡單的做法:索性不處理符號鏈接,讓用戶自己去看著辦吧。

聯網系統變得越來越複雜,問題也越來越難以解決了:

日期: Wed, 2 Jan 1991 16:14:27 PST 發信人: Ken Harrenstien 主題: Why find doesn't find anything (為什麼find什麼也找不到?) 收信人: UNIX-HATERS 我剛剛發現為什麼"find"不再工作了。 儘管"find"的語法非常噁心怪異,我還在勉強用它,以免幾小時泡在在迷宮似的文件目錄中去尋找文件。 在這個有NFS和符號鏈接存在的勇敢新世界裡,"find"沒用了。我們這裡的所謂文件系統是由眾多文件服務器和符號鏈接組成的一團亂麻,"find"哪個也不想去處理,甚至連選項也不提供... 結果是大量的搜索路徑被無聲無息 地忽略了。我注意到了這個,是在一個很大的目錄下搜索時結果一無所獲,最後發現是因為那個目錄是個符號鏈接。 我不想自己去檢查每一個交給find的搜索目錄——這他媽應該是find的工作。我不想去每次這類情況發生時都要去調查一下系統軟件。我不想浪費時間來和SUN或者整個Unix黨徒們做鬥爭。我不想用Unix。恨,恨,恨,恨,恨,恨,恨,恨。 ——Ken (感覺好些了,可還是有點惱)

如果想寫個複雜一點的shell腳本對找到的文件進行處理,結果往往會很奇怪。這是shell傳遞參數方式所產生的悲慘後果。

日期: Sat, 12 Dec 92 01:15:52 PST 發信人: Jamie Zawinski 主題: Q: what's the opposite of 'find?' A: 'lose' (問題:'find'的反義詞是什麼? 答案:丟失) 收信人: UNIX-HATERS 我想找出一個目錄下的所有的沒有對應.elc文件存在的.el文件。這應該不太難,我用的是find. 不過我錯了。 我先是這麼幹的: % find . -name '*.el' -exec 'test -f {}c' find: incomplete statement 噢,我記起來了,它需要個分號。 % find . -name '*.el' -exec 'test -f {}c'\; find: Can't execute test -f {}c: No such file or directory 真有你的,竟然沒去解析這個命令。 % find . -name '*.el' -exec test -f {}c \; 咦,似乎什麼也沒做... % find . -name '*.el' -exec echo test -f {}c \; test -f c test -f c test -f c test -f c .... 明白了。是shell把大括號給展開了。 % find . -name '*.el' -exec test -f '{}'c \; test -f {}c test -f {}c test -f {}c test -f {}c 嗯?也許我記錯了,{}並不是find使用的那個「替換成這個文件名」的符號。真的麼?... % find . -name '*.el' \ -exec test -f '{}' c \; test -f ./bytecomp/bytecomp-runtime.el c test -f ./bytecomp/disass.el c test -f ./bytecomp/bytecomp.el c test -f ./bytecomp/byte-optimize.el c .... 喔,原來如此。下面該怎麼辦呢?我想,我似乎可以試試"sed..." 可我忘記了一個深刻的哲理:「當遇到一個Unix問題的時候,有的人會想『我懂,我可以試試sed.』這下他們有兩個問題去對付了。」 試驗了五次,閱讀了sed手冊兩遍,我得到了這個: % echo foo.el | sed 's/$/c/' 於是: % find . -name '*.el' \ -exec echo test -f `echo '{}' \ | sed 's/$/c'` \; test -f c test -f c test -f c .... OK, 看來只能去試試所有shell引用的排列組合了,總會有一款和我意吧? % find . -name '*.el' \ -exec echo test -f "`echo '{}' \ | sed 's/$/c'`" \; Variable syntax. % find . -name '*.el' \ -exec echo test -f '`echo "{}" \ | sed "s/$/c"`' \; test -f `echo "{}" | sed "s/$/c"` test -f `echo "{}" | sed "s/$/c"` test -f `echo "{}" | sed "s/$/c"` .... 嗨,最後一個似乎有戲。我只需要這麼幹一下: % find . -name '*.el' \ -exec echo test -f '`echo {} \ | sed "s/$/c"`' \; test -f `echo {} | sed "s/$/c"` test -f `echo {} | sed "s/$/c"` test -f `echo {} | sed "s/$/c"` .... 別急,這是我想要的,可是你為什麼不把{}替換成文件名呢?你再仔細瞅瞅,{}兩邊不是有空格麼?你究竟想要什麼? 哦,等等。那個反單引號間的引用被當成了一個元素。 或許我能用sed把這個反單引號過濾掉。嗯,沒戲。 於是我用了半分鐘去想如何能運行"-exec sh -c..."之類的東西,終於出現了曙光,寫了一段emcas-lisp代碼去做這件事。這不困難,挺快的,而且工作了。 我真高興。我以為一切都過去了。 今天早上我洗澡的時候突然想到了另一種做法。我試了一次又一次,深深墜入了她的情網,意亂情迷,無法自拔。醉了。只有罕諾塔的Scribe實現曾給過我這樣的快感。我僅試了12次就找到了解法。對於每個遍歷到的文件它只產生兩個進程。這才是Unix之道! % find . -name '*.el' -print \ | sed 's/^/FOO-/'|\ sed 's/$/; if [ ! -f ${FOO}c]; then \ echo \ $FOO; fi/' | sh BWAAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH HAAAAHH!!!! —Jamie

OK, 在下水道裡玩捉迷藏挺有意思的吧?第8章就在歡聲笑語中這麼結束了。下回書我們就要開始編程了,還記得小時候那個可愛迷人的護士阿姨是怎麼對你說的麼?

「牛牛別怕,不疼的。」

沒有留言: