一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

Unix的缺陷

  我想通過這篇文章解釋一下我對 Unix 哲學(xué)本質(zhì)的理解。我雖然指出 Unix 的一個(gè)設(shè)計(jì)問題,但目的并不是打擊人們對 Unix 的興趣。雖然 Unix 在基礎(chǔ)概念上有一個(gè)挺嚴(yán)重的問題,但是經(jīng)過多年的發(fā)展之后,這個(gè)問題恐怕已經(jīng)被各種別的因素所彌補(bǔ)(比如大量的人力)。但是如果開始正視這個(gè)問題,我們也許就可以緩慢的改善系統(tǒng)的結(jié)構(gòu),從而使得它用起來更加高效,方便和安全,那又未嘗不可。同時(shí)也希望這里對 Unix 命令本質(zhì)的闡述能幫助人迅速的掌握 Unix,靈活的應(yīng)用它的潛力,避免它的缺點(diǎn)。
  通常所說的“Unix哲學(xué)”包括以下三條原則[Mcllroy]:

  1. 一個(gè)程序只做一件事情,并且把它做好。
  2. 程序之間能夠協(xié)同工作。
  3. 程序處理文本流,因?yàn)樗且粋€(gè)通用的接口。

  這三條原則當(dāng)中,前兩條其實(shí)早于 Unix 就已經(jīng)存在,它們描述的其實(shí)是程序設(shè)計(jì)最基本的原則 —— 模塊化原則。任何一個(gè)具有函數(shù)和調(diào)用的程序語言都具有這兩條原則。簡言之,第一條針對函數(shù),第二條針對調(diào)用。所謂“程序”,其實(shí)是一個(gè)叫 "main" 的函數(shù)(詳見下文)。

  所以只有第三條(用文本流做接口)是 Unix 所特有的。下文的“Unix哲學(xué)”如果不加修飾,就特指這第三條原則。但是許多的事實(shí)已經(jīng)顯示出,這第三條原則其實(shí)包含了實(shí)質(zhì)性的錯(cuò)誤。它不但一直在給我們制造無需有的問題,并且在很大程度上破壞前兩條原則的實(shí)施。然而,這條原則卻被很多人奉為神圣。許多程序員在他們自己的程序和協(xié)議里大量的使用文本流來表示數(shù)據(jù),引發(fā)了各種頭痛的問題,卻對此視而不見。

  Linux 有它優(yōu)于 Unix 的革新之處,但是我們必須看到,它其實(shí)還是繼承了 Unix 的這條哲學(xué)。Linux 系統(tǒng)的命令行,配置文件,各種工具之間都通過非標(biāo)準(zhǔn)化的文本流傳遞數(shù)據(jù)。這造成了信息格式的不一致和程序間協(xié)作的困難。然而,我這樣說并不等于 Windows 或者 Mac 就做得好很多,雖然它們對此有所改進(jìn)。實(shí)際上,幾乎所有常見的操作系統(tǒng)都受到 Unix 哲學(xué)潛移默化的影響,以至于它們身上或多或少都存在它的陰影。

  Unix 哲學(xué)的影響是多方面的。從命令行到程序語言,到數(shù)據(jù)庫,Web…… 計(jì)算機(jī)和網(wǎng)絡(luò)系統(tǒng)的方方面面無不顯示出它的影子。在這里,我會把眾多的問題與它們的根源 —— Unix哲學(xué)相關(guān)聯(lián)。現(xiàn)在我就從最簡單的命令行開始吧,希望你能從這些最簡單例子里看到 Unix 執(zhí)行命令的過程,以及其中存在的問題。(文本流的實(shí)質(zhì)就是字符串,所以在下文里這兩個(gè)名詞通用)

  一個(gè) Linux 命令運(yùn)行的基本過程

  幾乎每個(gè) Linux 用戶都為它的命令行困惑過。很多人(包括我在內(nèi))用了好幾年 Linux 也沒有完全的掌握命令行的用法。雖然看文檔看書以為都看透了,到時(shí)候還是會出現(xiàn)莫名其妙的問題,有時(shí)甚至?xí)馁M(fèi)大半天的時(shí)間在上面。其實(shí)如果看透了命令行的本質(zhì),你就會發(fā)現(xiàn)很多問題其實(shí)不是用戶的錯(cuò)。Linux 遺傳了 Unix 的“哲學(xué)”,用文本流來表示數(shù)據(jù)和參數(shù),才導(dǎo)致了命令行難學(xué)難用。

  我們首先來分析一下 Linux 命令行的工作原理吧。下圖是一個(gè)很簡單的 Linux 命令運(yùn)行的過程。當(dāng)然這不是全過程,但是更具體的細(xì)節(jié)跟我現(xiàn)在要說的主題無關(guān)。

  
  從上圖我們可以看到,在 ls 命令運(yùn)行的整個(gè)過程中,發(fā)生了如下的事情:

  1. shell(在這個(gè)例子里是bash)從終端得到輸入的字符串 "ls -l *.c"。然后 shell 以空白字符為界,切分這個(gè)字符串,得到 "ls", "-l" 和 "*.c" 三個(gè)字符串。
  2. shell 發(fā)現(xiàn)第二個(gè)字符串是通配符 "*.c",于是在當(dāng)前目錄下尋找與這個(gè)通配符匹配的文件。它找到兩個(gè)文件: foo.c 和 bar.c。
  3. shell 把這兩個(gè)文件的名字和其余的字符串一起做成一個(gè)字符串?dāng)?shù)組 {"ls", "-l", "bar.c", "foo.c"}. 它的長度是 4.
  4. shell 生成一個(gè)新的進(jìn)程,在里面執(zhí)行一個(gè)名叫 "ls" 的程序,并且把字符串?dāng)?shù)組 {"ls", "-l", "bar.c", "foo.c"}和它的長度4,作為ls的main函數(shù)的參數(shù)。main函數(shù)是C語言程序的“入口”,這個(gè)你可能已經(jīng)知道。
  5. ls 程序啟動(dòng)并且得到的這兩個(gè)參數(shù)(argv,argc)后,對它們做一些分析,提取其中的有用信息。比如 ls 發(fā)現(xiàn)字符串?dāng)?shù)組 argv 的第二個(gè)元素 "-l" 以 "-" 開頭,就知道那是一個(gè)選項(xiàng) —— 用戶想列出文件詳細(xì)的信息,于是它設(shè)置一個(gè)布爾變量表示這個(gè)信息,以便以后決定輸出文件信息的格式。
  6. ls 列出 foo.c 和 bar.c 兩個(gè)文件的“長格式”信息之后退出。以整數(shù)0作為返回值。
  7. shell 得知 ls 已經(jīng)退出,返回值是 0。在 shell 看來,0 表示成功,而其它值(不管正數(shù)負(fù)數(shù))都表示失敗。于是 shell 知道 ls 運(yùn)行成功了。由于沒有別的命令需要運(yùn)行,shell 向屏幕打印出提示符,開始等待新的終端輸入……

  從上面的命令運(yùn)行的過程中,我們可以看到文本流(字符串)在命令行中的普遍存在:

  • 用戶在終端輸入是字符串。
  • shell 從終端得到的是字符串,分解之后得到 3 個(gè)字符串,展開通配符后得到 4 個(gè)字符串。
  • ls 程序從參數(shù)得到那 4 個(gè)字符串,看到字符串 "-l" 的時(shí)候,就決定使用長格式進(jìn)行輸出。

  接下來你會看到這樣的做法引起的問題。

  冰山一角

  在《Unix 痛恨者手冊》(The Unix-Hater's Handbook, 以下簡稱 UHH)這本書開頭,作者列舉了 Unix 命令行用戶界面的一系列罪狀,咋一看還以為是脾氣不好的初學(xué)者在謾罵。可是仔細(xì)看看,你會發(fā)現(xiàn)雖然態(tài)度不好,他們某些人的話里面有非常深刻的道理。我們總是可以從罵我們的人身上學(xué)到一些東西,所以仔細(xì)看了一下,發(fā)現(xiàn)其實(shí)這些命令行問題的根源就是“Unix 哲學(xué)” —— 用文本流(字符串)來表示參數(shù)和數(shù)據(jù)。很多人都沒有意識到,文本流的過度使用,引發(fā)了太多問題。我會在后面列出這些問題,不過我現(xiàn)在先舉一些最簡單的例子來解釋一下這個(gè)問題的本質(zhì),你現(xiàn)在就可以自己動(dòng)手試一下。

  1. 在你的 Linux 終端里執(zhí)行如下命令(依次輸入:大于號,減號,小寫字母l)。這會在目錄下建立一個(gè)叫 "-l" 的文件。
    $ >-l
  2. 執(zhí)行命令 ls * (你的意圖是以短格式列出目錄下的所有文件)。

  你看到什么了呢?你沒有給 ls 任何選項(xiàng),文件卻出人意料的以“長格式”列了出來,而這個(gè)列表里面卻沒有你剛剛建立的那個(gè)名叫 "-l" 的文件。比如我得到如下輸出:

-rw-r--r-- 1 wy wy 0 2011-05-22 23:03 bar.c

-rw-r--r-- 1 wy wy 0 2011-05-22 23:03 foo.c

  到底發(fā)生了什么呢?重溫一下上面的示意圖吧,特別注意第二步。原來 shell 在調(diào)用 ls 之前,把通配符 * 展開成了目錄下的所有文件,那就是 "foo.c", "bar.c", 和一個(gè)名叫 "-l" 的文件。它把這 3 個(gè)字符串加上 ls 自己的名字,放進(jìn)一個(gè)字符串?dāng)?shù)組 {"ls", "bar.c", "foo.c", "-l"},交給 ls。接下來發(fā)生的是,ls 拿到這個(gè)字符串?dāng)?shù)組,發(fā)現(xiàn)里面有個(gè)字符串是 "-l",就以為那是一個(gè)選項(xiàng):用戶想用“長格式”輸出文件信息。因?yàn)?"-l" 被認(rèn)為是選項(xiàng),就沒有被列出來。于是我就得到上面的結(jié)果:長格式,還少了一個(gè)文件!

  這說明了什么問題呢?是用戶的錯(cuò)嗎?高手們也許會笑,怎么有人會這么傻,在目錄里建立一個(gè)叫 "-l" 的文件。但是就是這樣的態(tài)度,導(dǎo)致了我們對錯(cuò)誤視而不見,甚至讓它發(fā)揚(yáng)光大。其實(shí)撇除心里的優(yōu)越感,從理性的觀點(diǎn)看一看,我們就發(fā)現(xiàn)這一切都是系統(tǒng)設(shè)計(jì)的問題,而不是用戶的錯(cuò)誤。如果用戶要上法庭狀告 Linux,他可以這樣寫:

起訴狀

原告:用戶 luser

被告:Linux 操作系統(tǒng)

事由:合同糾紛

  1. 被告的文件系統(tǒng)給用戶提供了機(jī)制建立這樣一個(gè)叫 "-l" 的文件,這表示原告有權(quán)使用這個(gè)文件名。
  2. 既然 "-l" 是一個(gè)合法的文件名,而 "*" 通配符表示匹配“任何文件”,那么在原告使用 "ls *" 命令的時(shí)候,被告就應(yīng)該像原告所期望的那樣,以正常的方式列出目錄下所有的文件,包括 "-l" 在內(nèi)。
  3. 但是實(shí)際上原告沒有達(dá)到他認(rèn)為理所當(dāng)然的結(jié)果。"-l" 被 ls 命令認(rèn)為是一個(gè)命令行選項(xiàng),而不是一個(gè)文件。
  4. 原告認(rèn)為自己的合法權(quán)益受到侵犯。

  我覺得為了免去責(zé)任,一個(gè)系統(tǒng)必須提供切實(shí)的保障措施,而不只是口頭上的約定來要求用戶“小心”。就像如果你在街上挖個(gè)大洞施工,必須放上路障和警示燈。你不能只插一面小旗子在那里,用一行小字寫著: “前方施工,后果自負(fù)。”我想每一個(gè)正常人都會判定是施工者的錯(cuò)誤。

  可是 Unix 對于它的用戶卻一直是像這樣的施工者,它要求用戶:“仔細(xì)看 man page,否則后果自負(fù)。”其實(shí)不是用戶想偷懶,而是這些條款太多,根本沒有人能記得住。而且沒被咬過之前,誰會去看那些偏僻的內(nèi)容啊。但是一被咬,就后悔都來不及。完成一個(gè)簡單的任務(wù)都需要知道這么多可能的陷阱,那更加復(fù)雜的任務(wù)可怎么辦。其實(shí) Unix 的這些小問題累加起來,不知道讓人耗費(fèi)了多少寶貴的時(shí)間。

  如果你想更加確信這個(gè)問題的危險(xiǎn)性,可以試試如下的做法。在這之前,請新建一個(gè)測試用的目錄,以免丟失你的文件! 

  1. 在新目錄里,我們首先建立兩個(gè)文件夾 dir-a, dir-b 和三個(gè)普通文件 file1,file2 和 "-rf"。然后我們運(yùn)行 "rm *",意圖是刪除所有普通文件,而不刪掉目錄。

    $ mkdir dir-a dir-b

    $ touch file1 file2

    $ > -rf

    $ rm *

  2. 然后用 ls 查看目錄。

  你會發(fā)現(xiàn)最后只剩下一個(gè)文件: "-rf"。本來 "rm *" 只能刪除普通文件,現(xiàn)在由于目錄里存在一個(gè)叫 "-rf" 的文件。rm 以為那是叫它進(jìn)行強(qiáng)制遞歸刪除的選項(xiàng),所以它把目錄里所有的文件連同目錄全都刪掉了(除了 "-rf")。

  表面解決方案

  難道這說明我們應(yīng)該禁止任何以 "-" 開頭的文件名的存在,因?yàn)檫@樣會讓程序分不清選項(xiàng)和文件名?可是不幸的是,由于 Unix 給程序員的“靈活性”,并不是每個(gè)程序都認(rèn)為以 "-" 開頭的參數(shù)是選項(xiàng)。比如,Linux 下的 tar,ps 等命令就是例外。所以這個(gè)方案不大可行。

  從上面的例子我們可以看出,問題的來源似乎是因?yàn)?ls 根本不知道通配符 * 的存在。是 shell 把通配符展開以后給 ls。其實(shí) ls 得到的是文件名和選項(xiàng)混合在一起的字符串?dāng)?shù)組。所以 UHH 的作者提出的一個(gè)看法:“shell 根本不應(yīng)該展開通配符。通配符應(yīng)該直接被送給程序,由程序自己調(diào)用一個(gè)庫函數(shù)來展開。”

  這個(gè)方案確實(shí)可行:如果 shell 把通配符直接給 ls,那么 ls 會只看到 "*" 一個(gè)參數(shù)。它會調(diào)用庫函數(shù)在文件系統(tǒng)里去尋找當(dāng)前目錄下的所有文件,它會很清楚的知道 "-l" 是一個(gè)文件,而不是一個(gè)選項(xiàng),因?yàn)樗緵]有從 shell 那里得到任何選項(xiàng)(它只得到一個(gè)參數(shù):"*")。所以問題貌似就解決了。

  但是這樣每一個(gè)命令都自己檢查通配符的存在,然后去調(diào)用庫函數(shù)來解釋它,大大增加了程序員的工作量和出錯(cuò)的概率。況且 shell 不但展開通配符,還有環(huán)境變量,花括號展開,~展開,命令替換,算術(shù)運(yùn)算展開…… 這些讓每個(gè)程序都自己去做?這恰恰違反了第一條 Unix 哲學(xué) —— 模塊化原則。而且這個(gè)方法并不是一勞永逸的,它只能解決這一個(gè)問題。我們還將遇到文本流引起的更多的問題,它們沒法用這個(gè)方法解決。下面就是一個(gè)這樣的例子。

  冰山又一角

  這些看似微不足道的問題里面其實(shí)包含了 Unix 本質(zhì)的問題。如果不能正確認(rèn)識到它,我們跳出了一個(gè)問題,還會進(jìn)入另一個(gè)。我講一個(gè)自己的親身經(jīng)歷吧。我前年夏天在 Google 實(shí)習(xí)快結(jié)束的時(shí)候發(fā)生了這樣一件事情……

  由于我的項(xiàng)目對一個(gè)開源項(xiàng)目的依賴關(guān)系,我必須在 Google 的 Perforce 代碼庫中提交這個(gè)開源項(xiàng)目的所有文件。這個(gè)開源項(xiàng)目里面有 9000 多個(gè)文件,而 Perforce 是如此之慢,在提交進(jìn)行到一個(gè)小時(shí)的時(shí)候,突然報(bào)錯(cuò)退出了,說有兩個(gè)文件找不到。又試了兩次(順便出去喝了咖啡,打了臺球),還是失敗,這樣一天就快過去了。于是我搜索了一下這兩個(gè)文件,確實(shí)不存在。怎么會呢?我是用公司手冊上的命令行把項(xiàng)目的文件導(dǎo)入到 Perforce 的呀,怎么會無中生有?這條命令是這樣:

find -name *.Java -print | xargs p4 add

  它的工作原理是,find 命令在目錄樹下找到所有的以 ".Java" 結(jié)尾的文件,把它們用空格符隔開做成一個(gè)字符串,然后交給 xargs。之后 xargs 以空格符把這個(gè)字符串拆開成多個(gè)字符串,放在 "p4 add" 后面,組合成一條命令,然后執(zhí)行它。基本上你可以把 find 想象成 Lisp 里的 "filter",而 xargs 就是 "map"。所以這條命令轉(zhuǎn)換成 Lisp 樣式的偽碼就是:

(map (lambda (x) (p4 add x))
     (filter (lambda (x) (regexp-match? "*.Java" x))
             (files-in-current-dir)))

  問題出在哪里呢?經(jīng)過一下午的困惑之后我終于發(fā)現(xiàn),原來這個(gè)開源項(xiàng)目里某個(gè)目錄下,有一個(gè)叫做 "App Launcher.Java" 的文件。由于它的名字里面含有一個(gè)空格,被 xargs 拆開成了兩個(gè)字符串: "App" 和 "Launcher.Java"。當(dāng)然這兩個(gè)文件都不存在了!所以 Perforce 在提交的時(shí)候抱怨找不到它們。我告訴組里的負(fù)責(zé)人這個(gè)發(fā)現(xiàn)后,他說:“這些家伙,怎么能給 Java 程序起這樣一個(gè)名字?也太菜了吧!”

  但是我卻不認(rèn)為是這個(gè)開源項(xiàng)目的程序員的錯(cuò)誤,這其實(shí)顯示了 Unix 的問題。這個(gè)問題的根源是因?yàn)?Unix 的命令 (find, xargs) 把文件名以字符串的形式傳遞,它們默認(rèn)的“協(xié)議”是“以空格符隔開文件名”。而這個(gè)項(xiàng)目里恰恰有一個(gè)文件的名字里面有空格符,所以導(dǎo)致了歧義的產(chǎn)生。該怪誰呢?既然 Linux 允許文件名里面有空格,那么用戶就有權(quán)使用這個(gè)功能。到頭來因此出了問題,用戶卻被叫做菜鳥,為什么自己不小心,不看 man page。

  后來我仔細(xì)看了一下 find 和 xargs 的 man page,發(fā)現(xiàn)其實(shí)它們的設(shè)計(jì)者其實(shí)已經(jīng)意識到這個(gè)問題。所以 find 和 xargs 各有一個(gè)選項(xiàng):"-print0" 和 "-0"。它們可以讓 find 和 xargs 不用空格符,而用 "NULL"(ASCII字符 0)作為文件名的分隔符,這樣就可以避免文件名里有空格導(dǎo)致的問題。可是,似乎每次遇到這樣的問題總是過后方知。難道用戶真的需要知道這么多,小心翼翼,才能有效的使用 Unix 嗎?

  文本流不是可靠的接口

  這些例子其實(shí)從不同的側(cè)面顯示了同一個(gè)本質(zhì)的問題:用文本流來傳遞數(shù)據(jù)有嚴(yán)重的問題。是的,文本流是一個(gè)“通用”的接口,但是它卻不是一個(gè)“可靠”或者“方便”的接口。Unix 命令的工作原理基本是這樣:  

  • 從標(biāo)準(zhǔn)輸入得到文本流,處理,向標(biāo)準(zhǔn)輸出打印文本流。
  • 程序之間用管道進(jìn)行通信,讓文本流可以在程序間傳遞。

  這其中主要有兩個(gè)過程:

  1. 程序向標(biāo)準(zhǔn)輸出“打印”的時(shí)候,數(shù)據(jù)被轉(zhuǎn)換成文本。這是一個(gè)編碼過程。
  2. 文本通過管道(或者文件)進(jìn)入另一個(gè)程序,這個(gè)程序需要從文本里面提取它需要的信息。這是一個(gè)解碼過程。

  編碼的貌似很簡單,你只需要隨便設(shè)計(jì)一個(gè)“語法”,比如“用空格隔開”,就能輸出了。可是編碼的設(shè)計(jì)遠(yuǎn)遠(yuǎn)不是想象的那么容易。要是編碼格式?jīng)]有設(shè)計(jì)好,解碼的人就麻煩了,輕則需要正則表達(dá)式才能提取出文本里的信息,遇到復(fù)雜一點(diǎn)的編碼(比如程序文本),就得用 parser。最嚴(yán)重的問題是,由于鼓勵(lì)使用文本流,很多程序員很隨意的設(shè)計(jì)他們的編碼方式而不經(jīng)過嚴(yán)密思考。這就造成了 Unix 的幾乎每個(gè)程序都有各自不同的輸出格式,使得解碼成為非常頭痛的問題,經(jīng)常出現(xiàn)歧義和混淆。

  上面 find/xargs 的問題就是因?yàn)?find 編碼的分隔符(空格)和文件名里可能存在的空格相混淆 —— 此空格非彼空格也。而之前的 ls 和 rm 的問題就是因?yàn)?shell 把文件名和選項(xiàng)都“編碼”為“字符串”,所以 ls 程序無法通過解碼來辨別它們的到底是文件名還是選項(xiàng) —— 此字符串非彼字符串也!

  如果你使用過 Java 或者函數(shù)式語言(Haskell 或者 ML),你可能會了解一些類型理論(type theory)。在類型理論里,數(shù)據(jù)的類型是多樣的,Integer, String, Boolean, List, record…… 程序之間傳遞的所謂“數(shù)據(jù)”,只不過就是這些類型的數(shù)據(jù)結(jié)構(gòu)。然而按照 Unix 的設(shè)計(jì),所有的類型都得被轉(zhuǎn)化成 String 之后在程序間傳遞。這樣帶來一個(gè)問題:由于無結(jié)構(gòu)的 String 沒有足夠的表達(dá)力來區(qū)分其它的數(shù)據(jù)類型,所以經(jīng)常會出現(xiàn)歧義。相比之下,如果用 Haskell 來表示命令行參數(shù),它應(yīng)該是這樣:

data Parameter = Option String | File String | ...

  雖然兩種東西的實(shí)質(zhì)都是 String,但是 Haskell 會給它們加上“標(biāo)簽”以區(qū)分 Option 還是 File。這樣當(dāng) ls 接收到參數(shù)列表的時(shí)候,它就從標(biāo)簽判斷哪個(gè)是選項(xiàng),哪個(gè)是參數(shù),而不是通過字符串的內(nèi)容來瞎猜。

  文本流帶來太多的問題

  綜上所述,文本流的問題在于,本來簡單明了的信息,被編碼成為文本流之后,就變得難以提取,甚至丟失。前面說的都是小問題,其實(shí)文本流的帶來的嚴(yán)重問題很多,它甚至創(chuàng)造了整個(gè)的研究領(lǐng)域。文本流的思想影響了太多的設(shè)計(jì)。比如:

  • 配置文件:幾乎每一個(gè)都用不同的文本格式保存數(shù)據(jù)。想想吧:.bashrc, .Xdefaults, .screenrc, .fvwm, .emacs, .vimrc, /etc目錄下那系列!這樣用戶需要了解太多的格式,然而它們并沒有什么本質(zhì)區(qū)別。為了整理好這些文件,花費(fèi)了大量的人力物力。
  • 程序文本:這個(gè)以后我會專門講。程序被作為文本文件,所以我們才需要 parser。這導(dǎo)致了整個(gè)編譯器領(lǐng)域花費(fèi)大量人力物力研究 parsing。其實(shí)程序完全可以被作為 parse tree 直接存儲,這樣編譯器可以直接讀取 parse tree,不但節(jié)省編譯時(shí)間,連 parser 都不用寫。
  • 數(shù)據(jù)庫接口:程序與關(guān)系式數(shù)據(jù)庫之間的交互使用含有 SQL 語句的字符串,由于字符串里的內(nèi)容跟程序的類型之間并無關(guān)聯(lián),導(dǎo)致了這種程序非常難以調(diào)試。
  • XML: 設(shè)計(jì)的初衷就是解決數(shù)據(jù)編碼的問題,然而不幸的是,它自己都難 parse。它跟 SQL 類似,與程序里的類型關(guān)聯(lián)性很差。程序里的類型名字即使跟 XML 里面的定義有所偏差,編譯器也不會報(bào)錯(cuò)。Android 程序經(jīng)常出現(xiàn)的 "force close",大部分時(shí)候是這個(gè)原因。與 XML 相關(guān)的一些東西,比如 XSLT, XQuery, XPath 等等,設(shè)計(jì)也非常糟糕。
  • WebJavaScript 經(jīng)常被作為字符串插入到網(wǎng)頁中。由于字符串可以被任意組合,這引起很多安全性問題。Web安全研究,有些就是解決這類問題的。
  • IDE接口:很多編譯器給編輯器和 IDE 提供的接口是基于文本的。編譯器打印出出錯(cuò)的行號和信息,比如 "102:32 variable x undefined",然后由編輯器和 IDE 從文本里面去提取這些信息,跳轉(zhuǎn)到相應(yīng)的位置。一旦編譯器改變打印格式,這些編輯器和 IDE 就得修改。
  • log分析: 有些公司調(diào)試程序的時(shí)候打印出文本 log 信息,然后專門請人寫程序分析這種 log,從里面提取有用的信息,非常費(fèi)時(shí)費(fèi)力。
  • 測試:很多人寫 unit test 的時(shí)候,喜歡把數(shù)據(jù)結(jié)構(gòu)通過 toString 等函數(shù)轉(zhuǎn)化成字符串之后,與一個(gè)標(biāo)準(zhǔn)的字符串進(jìn)行比較,導(dǎo)致這些測試在字符串格式改變之后失效而必須修改。

  還有很多的例子,你只需要在你的身邊去發(fā)現(xiàn)。 

  什么是“人類可讀”和“通用”接口?

  當(dāng)我提到文本流做接口的各種弊端時(shí),經(jīng)常有人會指出,雖然文本流不可靠又麻煩,但是它比其它接口更通用,因?yàn)樗俏ㄒ蝗祟惪勺x (human-readable) 的格式,任何編輯器都可以直接看到文本流的內(nèi)容,而其它格式都不是這樣的。對于這一點(diǎn)我想說的是:  

  1. 什么叫做“人類可讀”?文本流真的就是那么的可讀嗎?幾年前,普通的文本編輯器遇到中文的時(shí)候經(jīng)常亂碼,要折騰好一陣子才能讓它們支持中文。幸好經(jīng)過全世界的合作,我們現(xiàn)在有了 Unicode。
  2. 現(xiàn)在要閱讀 Unicode 的文件,你不但要有支持 Unicode 的編輯器/瀏覽器,你還得有能顯示相應(yīng)碼段的字體。文本流達(dá)到“人類可讀”真的不費(fèi)力氣?
  3. 除了文本流,其實(shí)還有很多人類可讀的格式,比如 JPEG。它可比文本流“可讀”和“通用”多了,連字體都用不著。

  所以,文本流的根本就不是“人類可讀”和“通用”的關(guān)鍵。真正的關(guān)鍵在于“標(biāo)準(zhǔn)化”。如果其它的數(shù)據(jù)類型被標(biāo)準(zhǔn)化,那么我們可以在任何編輯器,瀏覽器,終端里加入對它們的支持,完全達(dá)到人類和機(jī)器都可輕松讀取,就像我們今天讀取文本和 JPEG 一樣。

  解決方案

  其實(shí)有一個(gè)簡單的方式可以一勞永逸的解決所有這些問題: 

  1. 保留數(shù)據(jù)類型本來的結(jié)構(gòu)。不用文本流來表示除文本以外的數(shù)據(jù)。
  2. 用一個(gè)開放的,標(biāo)準(zhǔn)化的,可擴(kuò)展的方式來表示所有數(shù)據(jù)類型。
  3. 程序之間的數(shù)據(jù)傳遞和存儲,就像程序內(nèi)部的數(shù)據(jù)結(jié)構(gòu)一樣。

  Unix 命令行的本質(zhì)

  雖然文本流引起了這么多問題,但是 Unix 還是不會消亡,因?yàn)楫吘褂羞@么多的上層應(yīng)用已經(jīng)依賴于它,它幾乎是整個(gè) InterNET 的頂梁柱。所以這篇文章對于當(dāng)前狀況的一個(gè)實(shí)際意義,也許是可以幫助人們迅速的理解 Unix 的命令行機(jī)制,并且鼓勵(lì)程序員在新的應(yīng)用中使用結(jié)構(gòu)化的數(shù)據(jù)。

  Unix 命令雖然過于復(fù)雜而且功能冗余,但是如果你看透了它們的本質(zhì),就能輕而易舉的學(xué)會它們的使用方法。簡而言之,你可以用普通的編程思想來解釋所有的 Unix 命令:

  1. 函數(shù):每一個(gè) Unix 程序本質(zhì)上是一個(gè)函數(shù) (main)。
  2. 參數(shù):命令行參數(shù)就是這個(gè)函數(shù)的參數(shù)。 所有的參數(shù)對于 C 語言來說都是字符串,但是經(jīng)過 parse,它們可能有幾種不同的類型
    • 變量名:實(shí)際上文件名就是程序中的變量名,就像 x, y。而文件的本質(zhì)就是程序里的一個(gè)對象。
    • 字符串:這是真正的程序中的字符串,就像 "hello world"。
    • keyword argument: 選項(xiàng)本質(zhì)上就是“keyword argument”(kwarg),類似 Python 或者 Common Lisp 里面那個(gè)對應(yīng)的東西,短選項(xiàng)(看起來像 "-l", "-c" 等等),本質(zhì)上就是 bool 類型的 kwarg。比如 "ls -l" 以 Python 的語法就是 ls(l=true)。長選項(xiàng)本質(zhì)就是 string 類型的 kwarg。比如 "ls --color=auto" 以 Python 的語法就是 ls(color=auto)。
  3. 返回值:由于 main 函數(shù)只能返回整數(shù)類型(int),我們只好把其它類型 (string, list, record, ...) 的返回值序列化為文本流,然后通過文件送給另一個(gè)程序。這里“文件”通指磁盤文件,管道等等。它們是文本流通過的信道。我已經(jīng)提到過,文件的本質(zhì)是程序里的一個(gè)對象。
  4. 組合:所謂“管道”,不過是一種簡單的函數(shù)組合(composition)。比如 "A x | B",用函數(shù)來表示就是 "B(A(x))"。 但是注意,這里的計(jì)算過程,本質(zhì)上是 lazy evaluation (類似 Haskell)。當(dāng) B “需要”數(shù)據(jù)的時(shí)候,A 才會讀取更大部分的 x,并且計(jì)算出結(jié)果送給 B。并不是所有函數(shù)組合都可以用管道表示,比如,如何用管道表示 "C(B(x), A(y))"?所以函數(shù)組合是更加通用的機(jī)制。
  5. 分支:如果需要把返回值送到兩個(gè)不同的程序,你需要使用 tee。這相當(dāng)于在程序里把結(jié)果存到一個(gè)臨時(shí)變量,然后使用它兩次。
  6. 控制流:main 函數(shù)的返回值(int型)被 shell 用來作為控制流。shell 可以根據(jù) main 函數(shù)返回值來中斷或者繼續(xù)運(yùn)行一個(gè)腳本。這就像 Java 的 exception。
  7. shell: 各種 shell 語言的本質(zhì)都是用來連接這些 main 函數(shù)的語言,而 shell 的本質(zhì)其實(shí)是一個(gè) REPL (read-eval-print-loop,類似 Lisp)。用程序語言的觀點(diǎn),shell 語言完全是多余的東西,我們其實(shí)可以在 REPL 里用跟應(yīng)用程序一樣的程序語言。Lisp 系統(tǒng)就是這樣做的。

  數(shù)據(jù)直接存儲帶來的可能性

  由于存儲的是結(jié)構(gòu)化的數(shù)據(jù),任何支持這種格式的工具都可以讓用戶直接操作這個(gè)數(shù)據(jù)結(jié)構(gòu)。這會帶來意想不到的好處。

  1. 因?yàn)槊钚胁僮鞯氖墙Y(jié)構(gòu)化的參數(shù),系統(tǒng)可以非常智能的按類型補(bǔ)全命令,讓你完全不可能輸入語法錯(cuò)誤的命令。
  2. 可以直接在命令行里插入顯示圖片之類的 "meta data"。
  3. Drag&Drop 桌面上的對象到命令行里,然后執(zhí)行。
  4. 因?yàn)榇a是以 parse tree 結(jié)構(gòu)存儲的,IDE 會很容易的擴(kuò)展到支持所有的程序語言。
  5. 你可以在看 email 的時(shí)候?qū)ζ渲械拇a段進(jìn)行 IDE 似的結(jié)構(gòu)化編輯,甚至編譯和執(zhí)行。
  6. 結(jié)構(gòu)化的版本控制和程序比較(diff)。(參考我的talk

  還有很多很多,僅限于我們的想象力。

  程序語言,操作系統(tǒng),數(shù)據(jù)庫三位一體

  如果 main 函數(shù)可以接受多種類型的參數(shù),并且可以有 keyword argument,它能返回一個(gè)或多個(gè)不同類型的對象作為返回值,而且如果這些對象可以被自動(dòng)存儲到一種特殊的“數(shù)據(jù)庫”里,那么 shell,管道,命令行選項(xiàng),甚至連文件系統(tǒng)都沒有必要存在。我們甚至可以說,“操作系統(tǒng)”這個(gè)概念變得“透明”。因?yàn)檫@樣一來,操作系統(tǒng)的本質(zhì)不過是某種程序語言的“運(yùn)行時(shí)系統(tǒng)”(runtime system)。這有點(diǎn)像 JVM 之于 Java。其實(shí)從本質(zhì)上講,Unix 就是 C 語言的運(yùn)行時(shí)系統(tǒng)。

  如果我們再進(jìn)一步,把與數(shù)據(jù)庫的連接做成透明的,即用同一種程序語言來“隱性”(implicit)的訪問數(shù)據(jù)庫,而不是像 SQL 之類的專用數(shù)據(jù)庫語言,那么“數(shù)據(jù)庫”這個(gè)概念也變得透明了。我們得到的會是一個(gè)非常簡單,統(tǒng)一,方便,而且強(qiáng)大的系統(tǒng)。這個(gè)系統(tǒng)里面只有一種程序語言,程序員直接編寫高級語言程序,用同樣的語言從命令行執(zhí)行它們,而且不用擔(dān)心數(shù)據(jù)放在什么地方。這樣可以大大的減小程序員工作的復(fù)雜度,讓他們專注于問題本身,而不是系統(tǒng)的內(nèi)部結(jié)構(gòu)。

  實(shí)際上,類似這樣的系統(tǒng)在歷史上早已存在過 (Lisp MachineSystem/38Oberon),而且收到了不錯(cuò)的效果。但是由于某些原因(歷史的,經(jīng)濟(jì)的,政治的,技術(shù)的),它們都消亡了。但是不得不說它們的這種方式比 Unix 現(xiàn)有的方式優(yōu)秀,所以何不學(xué)過來?我相信,隨著程序語言和編譯器技術(shù)發(fā)展,它們的這種簡單而統(tǒng)一的設(shè)計(jì)理念,有一天會改變這個(gè)世界。

it知識庫Unix的缺陷,轉(zhuǎn)載需保留來源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 久久国产乱子伦精品免费不卡 | 久久九色综合九色99伊人 | 在线观看亚洲 | 都市激情一区 | 国产手机国产手机在线 | www.久久精品视频 | 精品免费看| 国产精品hd| 91精品啪国产在线观看免费牛牛 | 久久精品国产999久久久 | 色视频免费看 | 欧美a色| 国产男人女人做性全过程视频 | 激情综合五月亚洲婷婷 | 国农村精品国产自线拍 | 久久天堂视频 | 亚洲不卡一区二区三区在线 | 手机看片自拍自拍自拍 | 国产91激情对白露脸全程 | 91九色视频 | 999热成人精品国产免 | 一区二区三区视频观看 | 精品日本一区二区三区在线观看 | 欧美黑人巨大xxxxxfreexxxxx | 美女黄网站色一级毛片 | 午夜免费的国产片在线观看 | 丁香在线 | 久久精品亚洲精品国产色婷 | 亚洲第一视频网 | 狠狠入 | 51精品视频免费国产专区 | 狠狠干成人| 91国语对白 | 亚洲国产精品成人综合色在线婷婷 | 日本aⅴ永久免费网站www | 黄视频网站大全 | 午夜视频在线观看www中文 | 高清视频一区 | 高清国产欧美一v精品 | 国产高清国产专区国产精品 | 欧美日韩亚洲国产千人斩 |