程式扎記: [Perl 學習手冊] CH05 : 輸入與輸出

標籤

2010年8月22日 星期日

[Perl 學習手冊] CH05 : 輸入與輸出

前言 : 
先前的章節或多或少已經介紹過輸入與輸出 (input/output 常簡寫為IO) 的一些用法. 現在就讓我們先將 "標準輸入" 當成 "鍵盤" 以及將 "標準輸出" 當作 "螢幕"吧. 

讀取標準輸入 : 
讀取標準輸入串流相當容易 ; 我們在前面已經用過 . 在純量語境下, 將會傳回輸入的下一列 : 

  1. $line = ;    # 讀取下一列  
  2. chomp($line);          #然後把尾端的換行符截掉  
如果在讀到檔案結尾 (end-of-file), 整列的輸入就會傳回 undef ; 這樣的特性在配合迴圈使用時, 可以很方便的跳離迴圈 : 

  1. while(defined($line = )) {  
  2.     print " I see: $line";  
  3. }  
第一列程式碼讀取了標準輸入後將它存入變數與檢察變數是否被定義, 但其實它有更簡潔的寫法 : 

  1. while() {  
  2.     print " I see: $_";  
  3. }  
從字面的意義就是 "讀取一列輸入, 看看它式不是真值, 若是則進入 while, 反之離開 while 並丟掉剛剛讀到的那一列!". 整列輸入 與 Perl 最常用的預設變數 $_ 之間並沒有特殊關連 ; 只是在上面的簡寫運用中, 輸入的內容剛好儲存在 $_ 變數裡. 另一個從串列語境下執行 , 它會傳回一個串列 - 串列的每個元素代表一列輸入的內容 : 

  1. foreach() {  
  2.     print "I see $_";  # 會等使用者在輸入檔案結束符後, 將回傳的串列一行行輸出.  
  3. }  
從這裡可以看出 foreach 與 while 迴圈的差異, while 迴圈處理使用者輸入是輸入一行處理一行, foreach 迴圈則會等到使用者輸入全部的內容後一次輸出. 

從鑽石運算符輸入 : 
程式的調用引數 (invocation argument) 通常是命令列上跟在程式名稱之後的幾個 "單字". 在下面例子裡, 命令列引數代表要依序處理的幾個檔案名稱 : 
$ ./my_program john ken peter 

這道命令的意思是, 執行 my_program 命令, 然後該命令應該處理檔案 john, ken 最後是檔案 peter. 若是不提供任何調用引數, 則該程式應該從標準輸入串流處理. 但有個特例 ; 如果某個引數是連字符 (-) , 那也代表標準輸入. 所以假如引數是 "john - ken" 的話, 代表式應用程式先處理 john, 接著是標準輸入最後才是 ken. 既然鑽石運算符可以從標準輸入讀取, 所以上面的範例是可以寫成 : 

  1. while(<>) {  
  2.     chomp;  
  3.     print "I see $_\n";  
  4. }  
這與前面範例相同, 好處是可以少打字, 而且你應該也注意到我們使用了 chomp 的預設用法 ; 不加引數時, chomp 會直接作用在 $_ 之上. 節省打次次數, 就從小地方做起吧. 

調用引數 : 
嚴格來說鑽石運算符並不會真的去檢查調用引數 ; 它靠的是 @ARGV 陣列. 這個陣列是先由 Perl 解譯器建立的特殊矩陣, 其內容就是調用引數所組成的串列. 而 @ARGV 的用法於其他陣列相同 ; 你可以把元素從它裡面 shift/pop 出去, 或是用 foreach 逐項加以處理. 你也可以檢查是否有引數是以連字號 (-) 開頭的, 然後將它們當成調用逐項處理. 因此你可以在使用鑽石運算符前, 對這個陣列進行加工以得到你要的結果. 

用printf 來編排輸出結果 : 
處理輸出結果時, 也許你會希望使用控制能力比 print 更強的函式. 事實上你可能已經習慣 C 的 printf 函式, 來產生編排過的輸出結果. 而Perl 裡有同名的函式提供類似的功能. 
printf 函式的引數包括 "格式字串" 即 "所要印出的資料串列" 格式. 格式字串式填空用的模板, 代表你要輸出的格式 : 
printf "Hi, %s : Your code will be ineffective after %d days!\n", $user, $days_to_die; 

而有關 printf 的相關使用說明, 你可以參考 這裡. 
一般來說你不會將陣列當成 printf 的引數. 這是因為陣列可以包含任意數目的元素, 而格式字串只會用到固定數目的元素 : 假如格式字串有三個轉換格式, 那就必須要有剛好三個元素可供使用. 不過沒有人規定你不准在程式執行時產生格式字串 ; 它們可以是任意的運算式. 想要把事做好, 需要一點技巧, 看來先把格式字串存進變數可能是不錯的建議 (尤其在除錯時): 

  1. my @items = qw(john peter ken);  
  2. my $format = "Item list: \n" . ("%10s\n" x @items);  
  3. printf ($format, @items);  
這裡使用了 x 算符 (參考第二章), 來複製你所指定的字串, 複製的次數與 @items 的個數相同. 這樣一來所產生的格式字串就是自動根據 @items 生成對應的格式字串而因此能更正常使用 printf 將結果輸出. 

檔案代號 : 
Perl 提供三種預設檔案代號 - STDIN, STDOUT 與 STDERR -- 都是產生 Perl process 時自動開啟的檔案或裝置. 當你需要其他檔案代號時, 可以使用 open (Opens a file using the specified file handle) 函式告訴 Perl, 要求作業系統為你程式開取與外接溝通的橋梁. 參考範例如下 : 

  1. open CONFIG, "john";  
  2. open CONFIG, ";  
  3. open FHANDLE, ">ken";  
  4. open LOG, ">>logfile";  
第一個與第二個例子都是開啟檔案 john 並進行讀取的動作. 
而第三個例子, 它會開啟檔案代號 FHANDLE 並輸出內容到新檔案 ken. 如果檔案已經存在則會清除原有檔案的內容. 
第四個例子, 說明你可以使用 ">>" 符號指名以附加方式開啟檔案. 換句話說如果檔案原本就存在, 那麼新的資料將會被附加到原有檔案內容之後 ; 如果檔案不存在, 就會建立一個新的檔案. 而在指名檔案名稱地方, 你可以使用任何的純量表示法 : 

  1. my $file_name = "john";  
  2. open HANDLE, "> $file_name"; 開啟檔案 john 進行覆蓋寫入動作  
在檔案開啟失敗時, 如果你嘗試從開啟失敗的檔案代號讀取資料, 將會立刻讀到檔案結尾. 如果試圖將資料寫入打開失敗的檔案代號, 則什麼事也不會發生. 事實上透過 -w 選項或是 warnings 編譯命令啟用警告功能, 那麼在開始檔案代號失敗時, Perl 就會發出警告, 或者你可以透過 open 函式的返回值判斷是否開啟檔案代號成功與否 : 

  1. my $success = open LOG, ">>logfile";  
  2. unless($success) {  
  3.     print "Open fail!\n";  
  4. else {  
  5.     print "Open success!\n";  
  6. }  
  7. close LOG;  
當你不需要該檔案代號時, 可以利用 close 函式 關閉它. 範例如下 : 
close HANDLE; 

所謂關閉檔案代號就是在告訴作業系統, 我們對該資料串流的處理已經全部完成, 所以請系統將尚未寫入磁碟的輸出寫到磁碟, 以免有人等著使用它. 如果你開啟某個檔案代號或是結束程式, Perl 會自動關閉原先的檔案代號. 

使用 die 來處理嚴重錯誤 : 
當 Perl 發生嚴重錯誤時, 你的程式應該要中止執行, 並列印錯誤訊息已告知原因. 這樣的功能我們可以用現成的 die 函式來達成, 它讓我們能夠自製 "嚴重錯誤訊息". 
die 函式會印出你所指定的訊息, 並且讓你的程式在非零的結束狀態立刻終止. 每個在 Unix 上執行的程式, 都會有一個結束狀態 (exit status), 用來告訴我們程式執行結果是否成功. 以執行其他程式為本務的程式 (像是工具程式 make), 會檢視那些程式的結束狀態, 來判斷是否一切順利. 所謂的結束狀態其實也只能以一個位元組來表示, 所以它能傳遞的訊息不多 ; 傳統上, 零代表成功, 非零代表失敗. 所以我們可以將前面的例子改寫成 : 

  1. if(!open LOG, ">>logfile") {  
  2.     die "Can't not open logfile: $!";  
  3. }  
  4. close LOG;  
如果 open 失敗, die 會終止程式執行, 裡面的 $! 式系統給人看的抱怨訊息. 一般來說當系統拒絕我們所要求的服務 (像是開啟某個檔案) , 它會給我們一個裡由 : 在這個例子可能是 "權限不足" 或 "檔案找不到" 之類 ; 那就是你在 C 或是其他語言以 perror 取得的字串. 因此將 $! 放在錯誤訊息裡幫助使用者除錯是個不錯主意. (基本上只有在系統服務要求失敗瞬間, $! 的值才有意義) 
除此之外 die 還會幫你做一件事, 它會自動將 Perl 程式名稱和列號附加在錯誤訊息的後面, 因此你就容易對源代碼進行除錯, 如果你不想要顯示列號於檔案名稱, 請在 die 函式訊息尾端加上換列字符, 就可以取消這樣的功能 : 

  1. if(!open LOG, ">>logfile") {  
  2.     die "Can't not open logfile: $!\n";  # 加上 "\n" 表示不顯示列號與檔案名稱.  
  3. }  
相同的如果你只是想提出警告訊息, 而不中斷程式的執行, 哪你可以使用 warn 函式. 

使用檔案代號 : 
當檔案代號以讀取模式開啟後, 你可以輕易的從它讀取一列資料, 就像從 STDIN 標準輸入串流讀取一樣, 參考範例如下 : 

  1. my $file_name = "test.txt";  
  2. if(!open TEST, $file_name) {  
  3.     die "Fail to open $file_name ($!)";  
  4. }  
  5. while() {  
  6.     chomp;  
  7.     print "I See > $_\n";  
  8. }  
  9. close TEST;  
正如你所看到的, 所謂的 "整列輸入運算符" 是由兩部分組成 ; 一對角括號 以及裡面的 檔案代號. 以寫入或是附加模式開啟的檔案代號, 可以在 print 或 printf 函式中使用, 使用時請直接放在關鍵字之後, 引數串列之前 : 

  1. print LOG "Output to logfile...\n"; # 輸出到 LOG  
  2. printf STDERR ("Already completed %d.\n", $done/$total * 100);  
原則上假如你不為 printf 提供檔案代號, 它的輸出就會送到 STDOUT. 不過你可以使用 select 函式 來改變預設的檔案代號. 一旦選擇了預設輸出的檔案代號, 程式就會一直以此方式運作. 但是這樣很容易讓後半段的程式碼變的混淆, 所以並不是一個好辦法. 因此當你指定預設的檔案代號使用完之後, 最好把它設回原本的預設值. 
將資料輸出到檔案代號預設上都會經過緩衝, 不過只要將特殊變數 $| 設為 1, 就會立刻出清資料. 所以如果要讓輸出的內容立即送到檔案便可以這麼做. 

沒有留言:

張貼留言

網誌存檔