1. 程式人生 > 實用技巧 >輸入與輸出

輸入與輸出

讀取標準輸入

之所以稱<STDIN>行輸入操作符,是因為它真的是每次讀取一行,用一對尖括號表示。被它包圍的檔案控制代碼表示資料來源

由於行輸入操作符在讀到檔案結尾,沒有可讀內容的時候會返回undef,所以藉此可以跳出迴圈。

while(defined($line = <STDIN>)){
    print "I saw $line";    
}

由於這樣的額操作很常見,Perl提供了簡寫形式:

while(<STDIN>){
    print "I saw $_";
}

完整展開的等效寫法:

while (defined
($_ = <STDIN>)){ print "I saw $_"; }

注意:

1.這個簡寫行為只有在上面這種寫法中才適用。如果把行輸入操作符放在其他任何地方,特別是單獨成行時,它不會讀取一行輸入並存到$_

唯獨while迴圈的條件表示式中只有行輸入操作符時,才會按照這個方式執行。換句話說,若條件表示式中寫了其他程式碼,此行為將失效。

2.行輸入操作符(<STDIN>)和Perl的預設變數$_之間並無直接關聯,只是在這個簡寫裡,輸入的內容恰好存到預設變數中而已。

比較這兩個迴圈的差別:

while(<STDIN>){
    print
"I saw $_"; }

foreach(<STDIN>){
    print "I saw $_";
}

區別:

while迴圈裡,Perl每次讀取一行輸入,把它存到某個變數並執行迴圈體,而後再嘗試獲取後續輸入。

但在foreach迴圈裡,行輸入操作符會按列表上下文處理,所以要做的第一件事就是把所有的輸入都讀進來,按列表返回。資料量小的話沒關係,如果輸入來自一個400M的檔案,就要準備相應大小的記憶體來安頓!所以沒啥特殊情況,儘量還是使用while迴圈,一次處理一行。

來自鑽石操作符的輸入

另一種讀取輸入的方法:鑽石操作符<>

一般來說,“鍵盤”就是標準輸入,“螢幕”就是標準輸出。

鑽石操作符<>,能讓程式在處理呼叫引數時,提供類似於標準Unix工具程式的功能。

如果想用Perl編寫類似catsedawksortgreplpr之類的工具程式,一邊從標準輸入拿資料,一邊輸出結果到標準輸出,鑽石操作符將會是你的好幫手。

但如果要支援複雜的引數,單單靠鑽石操作符也是不行的。

$ ./my_program fred barney betty

這條命令的含義:

執行當前目錄的my_program程式,讓它依次處理檔案fredbarneybetty(當然,這樣寫的話,檔案也要在程式執行的目錄下)

若不提供任何引數,程式會從標準輸入流採集資料(也就是“鍵盤”)。

如果引數是連字元(-),則表示從標準輸入(也就是“鍵盤”)讀取資料。

所以,假如呼叫引數是fred - betty,那麼程式應該先處理檔案fred,然後處理標準輸入流中提供的資料,最後才是檔案betty

鑽石操作符是行輸入操作符的特例,不過它並不是從鍵盤取得輸入,而是從使用者指定的位置讀取。

while (defined($line = <>)){
    chomp($line);
    print "It was $line that I saw!\n";
}

鑽石操作符只有在碰到所有輸入的結尾時才會返回undef(然後程式就跳出while迴圈了)。

當前正在處理的檔名會被儲存在特殊變數$ARGV中。如果當前是從標準輸入獲得資料,那麼當前檔名就會是連字元-”。

由於鑽石操作符是行輸入操作符的一種特例,因此可以只用之前的簡寫,將輸入讀取到預設變數$_裡:

while (<>){
    chomp;  #不加引數時,chomp會直接作用在$_上
    print "It was $_ that I saw!\n";
}

雙鑽石操作符

5.22版本的時候,Perl修復了鑽石操作符的一個問題:如果命令列上傳入的檔名帶有特殊字元,比如|,就會引發管道操作Perl會開啟管道,從外部程式的輸出結果讀取輸入內容。所以這個過程啟動另外一個外部程式,而非開啟某個名字的檔案

use v5.22;

while (<<>>){
    chomp;  
    print "It was $_ that I saw!\n";
}

如果是Perl 5.22及以後的版本,最好預設使用雙鑽石操作符。

呼叫引數

從技術上講,鑽石操作符並不會檢查命令列引數,它看到的不過是放在@ARGV數組裡面的資料。

@ARGV是由Perl直譯器事先建立的特殊陣列,並在程式執行前根據命令列引數初始化。所以本質上它的使用方式和其它陣列沒有什麼不同(除了用了奇怪的全大寫名稱之外),只不過在程式開始執行時@ARGV裡就已經塞滿了呼叫引數。

既然@ARGV和其他陣列一樣,就可以用shift移出@ARGV中的元素,或者用foreach逐個處理,也可以檢查是否有引數是以連字元(-)開頭的,然後將它們當成呼叫選項處理(就像Perl對待自己的-w選項一樣)。

鑽石操作符會檢視陣列@ARGV,然後決定該用哪些檔名,如果它找到的是空列表(也就是執行時沒有給引數),就會改用標準輸入流,否則,就會使用@ARGV裡的檔案列表。

NOTE

程式執行之後,只要尚未使用鑽石操作符,就可以對@ARGV動點手腳,可以這樣處理三個特定的檔案,不管使用者在命令列引數中制定了什麼:

@ARGV = qw# larry moe curly #;   #強制讓鑽石操作符只讀取這三個檔案
while(<>){
    chomp;
    print "It was $_ that I saw.\n";
}

輸出到標準輸出

▲直接使用陣列和使用陣列內插在列印效果上是不同的:

print @array;  #元素間沒有空格分隔

print "@array";  #元素間有空格分隔

注意:

在使用陣列內插的寫法時,如果每個元素都是以換行符結尾,會輸入如下結果:

fred
 barney
 betty

因為Perl把陣列內插到字串中時,會在每個元素之間加上空格(實際上是特殊變數$定義的內容,預設是空格字元)。

print處理的是待列印的字串列表,因此它的引數會在列表上下文中被執行。

而鑽石操作符(行輸入操作符的特殊形式)在列表上下文中會返回由許多輸入行組成的列表,所以她們可以配合工作。

print <>;  #相當於Unix下的/bin/cat命令
print sort <>;  #相當於Unix下的/bin/sort命令

print後面的括號其實可有可無。原則:除非會改變表示式意義,否則括號可以省略。

print("Hello,world!\n");
print "Hello,world!\n";

假如print呼叫看起來像函式呼叫,那它就是一個函式呼叫。

呼叫函式時,函式名後面必須緊接著一對括號,包含呼叫函式的引數。

print (2+3);

它會輸出5,然後和其他函式一樣返回某個值。print的返回值不是真就是假,代表print是否執行成功,通常就是返回1

$result = print("hello, world!\n");  #返回1

注意:

print (2+3)*4  #糟糕!會列印5

它會列印5,然後Perl會取得print的返回值1,再將這個值乘以4,然後又將這項乘積丟失。

沒有括號的時候,print是列表操作符,會把其後的列表裡的所有東西全都輸出來。

假如print後面緊跟著左括號,它就是一個函式呼叫,只會將括號內的東西輸出來。

因為上面的程式碼有括號,所以就相當於這樣寫:

(print(2+3))*4;  #糟糕!

如果使用了-w 或者加上use warningsPerl就會給出提醒。

修復的辦法:

print ((2+3)*4);

printf格式化輸出

printf是控制能力比print更強的操作符。

printf操作符的引數包括“格式字串”及“要輸出的資料列表”。格式字串就是用來填空的模板,代表你想要的輸出格式。

printf "Hello, %s; your password expires in %d days!\n",$usr, $days_to_die;

格式字串裡可以有多個轉換,每種轉換都會以百分符號(%)開頭,然後以某個字母結尾(在這兩個符號之間可以存在另外的格式定義)。

後面的列表裡元素的個數應該和轉換的數目一樣多,如果數目不一樣,就無法正確執行。

printf常用轉換格式:(可以檢視perlfunc文件的<sprintf>部分)

1%g

要以恰當的形式輸出某個數字,可以使用%g,它會按需要自動輸出浮點數、整數甚至是指數形式。

printf "%g %g %g\n", 5/2,51/17,51**17; #2.5 3 1.0683e+029

2%d%x%o

%d表示十進位制整數,它會捨去小數點後的數字。注意,它會無條件截斷,而非四捨五入。

printf "in %d days!\n", 17.85;  #輸出:in 17 days!

%x表示十六進位制數,%o表示八進位制數。

printf "in %x days!\n",17;  #輸出:in 11 days!
printf "in %o days!\n",17;  #輸出:in 21 days!

3)指定寬度,如果資料太長,欄位會按需要自動擴充套件。

printf "%6d\n", 42;  #輸出:````42(`代表一個空格)
printf "%3d\n", 2001.95;  #輸出:2001

4%s

%s代表字串格式,它的功能其實就是字串內插,但同時還支援設定字元寬度。

printf "%10s\n", "wilma";  #輸出:`````wilma

如果寬度欄位是負數,則表示左對齊(適用於上述各種轉換格式):

printf "%-15s\n", 'flintstone';  #輸出:flintstone`````

5%f

%f表示浮點數轉換格式,它可以按需四捨五入,甚至可以指定小數點後的位數:

printf "%12f\n",6*7+2/3;  #輸出:```42.666667
printf "%12.3f\n",6*7+2/3;  #輸出:``````42.667
printf "%12.0f\n",6*7+2/3;  #輸出:``````````43