Perl 正迅速地成為一般系統管理員和程式設計者所必備的關鍵工具。 然而,211頁精美排版的Perl 5文件很容易就會嚇到使用者。你可能會 自問:「我要從那裡開始?」還有「我須要知道這文件裡的多少內容才能寫出一 個 Perl 的程式?」 嗯...你可以作的最容易的事情之一就是看別人如何解決一個簡單的 問題。例如,指定一個唯一的 user ID 給新的使用者這項常見的系統管理工 作來說,你必須先找出目前系統中指定給使用者的最大 user ID 為何,然後就 選下一個更大的數字即可。 在我們一步步達到最後目標前,先看一些簡單的問題及解決的方法。 首先,我們看看要如何將 who 這個指令所輸出結果的第一欄給印出來。 who | perl -ne '@F = split; print "$F[0]\n";' 從 who 這指令所輸出的結果變成了 Perl 的輸入。"-n" 會告訴 Perl 要一行行執行某些程式碼,並將每一行放入 "$_" 這變數中。"-e" 就會提供這 "某些程式碼"。而我們可以(且通常會)把兩個參數合併成上面那個樣子。 在這個例子中,我們有兩個 Perl 的敘述: 一個 split,及一個 print。 split 會將 $_ 變數中的內容分開成一串的字(用空白當作字與字間的界定符 號)。而 @F 這個陣列就會接收這一串字。 然後 print 就會把陣列中的第一個元素印出來,後面並加上一個換行 ( \n ) 符號。需要注意的是, @F 這個陣列的第一個元素是用 $F[0] 來表示 的,因為元素存放的位置是從 0 開始 ( 很像 C 的陣列形態 )。 若你要再省些打字的工夫,可以把 split 這個動作放入命令的參數中: who | perl -ane 'print "$F[0]\n"' 在此所加的 "-a" 是告訴 Perl 自動將 $_ 的內容 split 之後放到 @F 這陣列裡面, 和我們在前一個例子中所作的事情一模一樣。 為了再多省一些打字的工夫,我們可以加 "-l", 這參數一次可以作到以下 兩件事: 1. 它會在我們的程式讀到 $_ 之前把其中的換行符號給刪掉。 2. 它會在那字串被印出來前加一個換行的符號在後面。 這使得我們剛才看到的那小小命令列的範例變成這樣: who | perl -lane 'print $F[0]' 如果還要再省下一點點的打字,讓我們把 "-n" 換成 "-p" 就會告訴 Perl 在程式執行到最後,不論 $_ 變數中的話內容為何,都把它印出來: who | perl -lape '$_ = $F[0]' 嗯,好了,剛才這麼作的確也為你少打了一個字。不過有比沒有好,至少若你 在接下來的五年內每天都省一個字的話,那也省很多了。不過,誰知道呢? 剛才所用的 Perl 命令列的形式其效果等同於以下的 Perl 程式: #!/usr/bin/perl $\ = $/; # from -l while (<>) { # from -p chop; # from -l @F = split; # from -a $_ = $F[0]; # argument to -e print; # from -p } 我想你也發現了,從命令列輸入這麼少幾個字就可以取代一段程式碼的作 用。 $\ 這個變數所包含的是給每個 print 動作尾端附加上去的結尾, 很像 awk 中的 ORS 變數。未設定時,此變數的值是一個空字串,表示 print 會完全照 你的要求來運作。 在此,我們把 $/ 的值設給它,就是"輸入記錄分隔符號" ( input record seperator ),相當於 awk 中的 RS 變數。此變數的原值是 "\n"。這使得輸出的 分隔符號和輸入分隔符號成為同一個,而 print 就會自動地添加一個換行符號在 後面。 嗯, 有關於 who 這個命令的程式也講得差不多了, 讓我們繼續來看我們 本來真正要作的工作吧 : 分析整個 passwd 檔案以找出使用者 ID 的最大值。 passwd 檔案和 who 這命令的輸出所不同的地方在於檔案中的欄位是用 冒號作為分隔符號而非空格。不過這也沒問題,只消給 Perl 指定另一個分隔符號 就行了 : perl -aF: -lne 'print $F[0]' /etc/passwd 這個也確實給了我們一列使用者的帳號名。"-F" 將冒號定義成分隔符號 。注意到我將 "-a" 移到前面與 "-F" 相鄰,那是因為對我來說這兩者邏輯上是應 該一起出現的話 -- 除非有 split 的動作,否則分隔符號就沒有意義了。 如果你有用 Yellow Pages,呃,我是指 NIS,那你就得從那裡取得資料而 不是從 passwd 檔裡取,這樣子才會得到比較有趣的結果。 ypcat passwd | perl -aF: -lne 'print $F[0]' 在此, ypcat 這命令在標準輸出產生了一個像 password 的檔案,我們 剛才的 perl 指令就會很愉悅地將這些東西抓進來處理,就好像是在處理這機器 上自己的 /etc/passwd 檔案一樣。 但是這些是使用者的帳號名,而非使用者 ID。那是在第三個欄位,可以 用 $F[2] 得到 (再次提醒,因為是從 0 開始數,所以第三個就是在 2 )。只要 稍稍修改一下,我們就可得到所要的結果了。 perl -aF: -lne 'print $F[2]' /etc/passwd 現在我們已經得到了一列數字。使得我們離目標又更接近了一步。我 們要決定最大的那個數字,並印出比它大 1 的下一個數目。 因此,我們就要加入一個叫 $max 的純量變數。一開始, $max 未被定 義,所以當它被拿來跟其他數目比較時,看來就好像是 0 一樣。所以我們的工作 就是把每個使用者 ID 和 $max 比大小,若大於 $max, 則就將 $max 的值設成 那個數字: perl -aF: -lne '$max = $F[2] if $max < $F[2]; 在這邊,只要上述的條件成立,我們便將此數目字指定給 $max。在這樣 的情況下,下面的條件: $max < $F[2] 在每次迴圈執行時都要檢驗一次,若結果為真,則有指定變數值的動作產生。這 是 Perl 其中一個邏輯條件句是由右到左而非從左到右的地方。 現在這命令已經讓人覺得有點長了,所以讓我們把它轉成下面這個程式: #!/usr/bin/perl $\ = $/; while (<>) { chop; @F = split /:/; $max = $F[2] if $max < $F[2]; print $max; } OK,這又讓我們更進一步了。然而我們還是需要將 /etc/passwd 這檔 案餵給我們的程式,這工作若用命令列來作可是個有點重的擔子。我們索性直接 從程式中打開檔案算了。 #!/usr/bin/perl open(PASSWD,"/etc/passwd"); $\ = $/; while () { chop; @F = split /:/; $max = $F[2] if $max < $F[2]; print $max; } 這裡的 open() 指令會創造一個 filehandle 以便打開 /etc/passwd 並 讀取其內容。 對於那些 YP 的使用者們,只要多打幾個字就行了。 #!/usr/bin/perl open(PASSWD,"ypcat passwd|"); $\ = $/; while () { chop; @F = split /:/; $max = $F[2] if $max < $F[2]; print $max; } Perl 對待命令的輸出就好比它是個檔案一樣。在後面加上 "|" 可以指示出 命令的存在 (而非檔名)。與我們前面命令列的方法中所用的 "|" 有異曲同工之妙。 前幾個程式的輸出結果是一連串代表所能找到的最大使用者 ID 的數目字。 而我們真正要的是將最後一個數字印出來。喔,不! 我收回剛才所說的,因為我們真正 所須要的數字要比剛提到的那個還大 1。這樣對程式會有什麼影響呢? 簡單 -- 只要 把 print 移到迴圈外面: #!/usr/bin/perl open(PASSWD,"/etc/passwd"); # or YP equivalent $\ = $/; while () { chop; @F = split /:/; $max = $F[2] if $max < $F[2]; } print $max + 1; 別忘了加一,才會得到我們所要的結果。 終於,我們可以把這程式存入檔案中,並使它變成可以執行的檔案,再放到 我們 $PATH 環境變數中所設的目錄下面,然後當我們每次須要用它的時候,只要執 行它,就可以得到正確的數字了。 或者,幾乎是正確的數字。在某些情況下,有的系統 ( 像我測試這程式用 的 SunOS ) 會有一個使用者叫 nobody 佔用了一個很大的 user ID 值。如果你在 你的系統上跑這個程式且每次都得到 65535 這答案,原因就如同上述。 我們須要在算這最大數值時設個門檻。但是怎麼作? 嗯,如果 $F[2] 超過了我們所指定的數字 (例如, 30000), 則就不應該把 這個值設給 $max 變數。這表示我們只要把 if 的條件句變複雜一些就行了: #!/usr/bin/perl open(PASSWD,"/etc/passwd"); # or YP equivalent $\ = $/; while () { chop; @F = split /:/; $max = $F[2] if $F[2] < 30000 and $max < $F[2]; } print $max + 1; 好了! 這樣就定案了(希望如此)。至少這在 SunOS 下能用就是了。 所以,這個 "小" 工作並沒有想像中的那麼小, 但是至少我們可以只用幾 行 Perl 程式把它打發掉。如果你不介意有點長的命令列的話,事實上我們也可以 把它放到命令列裡面去: perl -aF: -lne '$m=$F[2] if $F[2]<30000 and $m<$F[2]; END { print $m+1 }' /etc/passwd 這有趣的地方在於 END 這個區塊敘述自動放到迴圈的外面,就像我們在 前面的程式中所作的一樣。 如果你是 Perl 的新手,或許你會想要找本好書來看看。我會推薦你以下 兩本書。不過因為我參與了這兩本書的寫作,所以這樣的介紹可能有瓜田李下之嫌 。 :) Learning Perl (O'Reilly and Associates, ISBN 1-56592-042-2) 是一本介紹這 個語言的書,還有一些練習與答案。本書適合那些對 UNIX 有點了解但又不是很了 解的廣大群眾。不過在打開本書鑽研進去前,多少還是有點寫程式的基礎最好。 Programming Perl (O'Reilly and Associates, ISBN 0-937175-64-1)相對來說是 一本較完整且具參考價值的書。是由我和 Perl 的發明者 -- Larry Wall 合寫的。 你也可能在書中發現一些另人一知半解的教學,還有長或實用的範例。這本書是適合 給較進階的使用者看的,若你沒有像我一樣從 1977 年就開始接觸 UNIX 至今,那麼 對你來說有些內容可能有點難。 還有一個很好的 USENET 上的新聞群組叫 comp.lang.perl (譯註), 有很 多 Perl 高手常在上面出現,包括 Larry Wall (當然還有敝人在下我)。若你無法 接觸到 Usenet 的話,寫封信到 perl-users-request@virginia.edu 去要求加入一 個 mailing list 吧。 註: 自 1995 Aug. 8th 後, comp.lang.perl 已被兩個相對應的新聞群組取代,它們 是 comp.lang.perl.misc 及 comp.lang.perl.announce。