Perl讀取標準輸入、讀取文件輸入<>和chomp函數

讀取標準輸入<STDIN>

<STDIN>表示從標準輸入中讀取內容,若是沒有,則等待輸入。<STDIN>讀取到的結果中,若是沒有意外,都會自帶換行符。shell

例如,test.plx文件內容:數組

#!/usr/bin/perl
#
$line=<STDIN>;
if($line eq "\n"){
        print "blank line\n";
} else {
        print "not blank: $line"
}

注意上面的else語句中,$line後面沒有加換行符,由於<STDIN>自帶換行符。bash

下面的命令,將等待輸入和回車。若是直接回車,則if條件爲真。函數

perl test.plx

下面是和bash shell交互。性能

echo "hello" | perl test.plx
echo -e "haha\nheihei" | perl test.plx

注意上面第二條語句中,heihei會被忽略,由於上面的操做是標量上下文(上下文的概念,之後會解釋),在發現換行符的時候,結束輸入的讀取,因此看到haha後面的"\n"就結束了。命令行

由於<STDIN>讀取的是標準輸入,因此若是要經過它讀取文件內容,須要使用shell的重定向功能。例如,讀取a.txt文件的內容到<STDIN>:code

perl test.plx <a.txt

另外,<STDIN>在標量上下文中返回的是某一行,可使用while來遍歷多行,但在遍歷時要書寫準確:對象

# 錯誤遍歷
$line = <STDIN>;
while(defined($line)){
    print $line;
}

# 正確遍歷
while(defined($line = <STDIN>)){
    print $line;
}

第一種寫法會無限循環輸出讀取的第一行(\n前面的行),由於$line被賦值爲該第一行,且再也不改變,因爲defined()返回真,會使得while無限循環。進程

第二種寫法每次讀取一行,每讀取一行作下讀取的位置標記方便下次讀取(也就是說<STDIN>是可迭代對象),直到讀取完最後一行返回undef使得defined返回false,結束循環。內存

因爲<STDIN>在讀取到最後一行後會返回undef,因此能夠簡寫上面的第二種方式:

# (建議寫法)
while(<STDIN>){
    print $_;
}

# 或(不建議寫法)
foreach(<STDIN>){
    print $_;
}

只是須要注意上面的兩種簡寫方式,每次讀取的行並非直接賦值給$_,而是在每次迭代過程當中賦值的。換句話說,上面的<STDIN>$_沒有直接關係,不像$line=<STDIN>這種賦值,直接在讀取的時候就賦值給$line,也正由於不是直接賦值給變量,才能循環讀取下去。

(本段涉及到上下文和數組的概念,暫時還沒介紹,如不理解,可先略過)上面的while和foreach有巨大的差異,while後面是標量上下文,foreach後面是列表上下文。這意味着while是每次從文件中讀取一行,打上位置標記以便下次讀取,而後進入循環體。而foreach由於操做目標是列表,它會一次性將文件中全部行讀取到內存,而後看成列表進行遍歷,因此性能很是差,例如400M的文件,也許須要1G的內存,由於perl會預估先分配足夠多的內存以便後續再也不由於分配內存的事而中斷進程。上面的過程,使用下面的形式描述,就很容易理解了:

$line = <STDIN>;    # 一次讀一行,性能好
@lines = <STDIN>;   # 一次讀全部,性能差

<STDIN>會帶有換行符,一般都會加上chomp()操做符去掉換行符,關於chomp,見下文。

讀取文件輸入<>

perl中使用兩個尖括號符號表示讀取來自文件的輸入,例如從命令行中傳遞文件做爲輸入源。這個符號被稱爲"鑽石操做符"。

例如,test.plx程序內容以下:

#!/usr/bin/perl

while(defined($line = <>)){
    print $_;
}

或者簡寫的:

#!/usr/bin/perl

while(<>){
    print $_;
}

而後在perl程序的命令行中指定輸入文件:

$ ./test.plx test.log

或者使用@ARGV數組指定輸入文件:

#!/usr/bin/perl

@ARGV=qw(test.log);

若是想要讀取標準輸入的數據,則在命令行中使用短橫線-

$ echo -e "haha\nheihei" | ./test.plx -

通常來講,while循環中使用<STDIN><>讀取輸入後(也包括open關鍵字打開文件再讀取行的狀況),第一行就是去除行尾的換行符,因此大多數都採用以下通用格式:

while(<>){
    chomp;
    COMMANDS;
}

while(<STDIN>){
    chomp;
    COMMANDS;
}

上面的chomp沒有參數,因此採用默認變量$_,也就是迭代中的每一行。

關於<>的機制,請繼續閱讀下文的<<>>

chomp()函數

這個函數用於去掉字符串或者讀取到的標準輸入的一個 換行符。注意關鍵字:字符串、一個、換行符。

注意,這個函數是在原處修改字符串。但這個函數有本身的返回值:

  • 若是能去掉換行符,則返回移除的字符數,也就是數值1。這是個沒什麼用的返回值,由於咱們都已經知道了;
  • 若是沒有換行符,則返回數值0;
  • 若是結尾有兩個換行符,則只去掉一個。

比較下面兩個程序,它們會從標準輸入中讀取:

$line=<STDIN>;
print $line;

$line=<STDIN>;
chomp($line);
print $line;

或者下面的例子,它直接操做一個已有的字符串:

$foo="hello world!\n"
chomp($foo);
print $foo;

字符串賦值操做和chomp()函數能夠結合在一塊兒。

chomp($line=<STDIN>);
chomp($line="hello world!\n");
print $foo;

但注意,chomp()是有返回值的。下面返回的是數值1。

print chomp($line="hello world!\n");

<<>>

實際上,除了<>還有<<>>。它們之間有區別,目前爲止尚未介紹文件句柄和ARGV變量,因此能看懂則看,看不懂則過。

<>會隱式打開來自@ARGV數組中的參數文件,打開方式是兩參數格式的open,相似於open "FH","$ARGV[N]"。正常狀況下這沒什麼問題,但可能會成致命的危險。例如Perl腳本文件a.pl內容以下:

#!/usr/bin/perl
while(<>){print}

若是執行該腳本的方式爲:

$ perl a.pl "rm -rfv * |"

其中rm -rfv * |被看成一個參數收集到@ARGV數組中,由於<>以兩參數模式的方式隱式打開文件,這等價於:

open FH,"rm -rfv *|";

這表示先打開一個管道,再執行rm -rfv *命令,並將該命令的輸出經過管道傳遞供<>讀取。

再看下面的示例,它們是等價的。

$ perl a.pl "ls /tmp |"
$ ls /tmp | perl a.pl

這在寫一行式perl程序的時候比較方便:

perl -pe '' "ls /tmp |"
ls /tmp | perl -pe ''

因此,以<>的方式打開@ARGV中的文件時,由於沒法保證這個數組中的參數必定是文件,因此可能會出現危險操做。

<<>>則能避免這個問題,它隱式地以三參數的open打開@ARGV中的文件。相似於:

open FH,"<","$ARGV[N]";

它限定了第二個參數是輸入重定向操做,因此它保證了@ARGV中的參數必須是文件,不然就會報錯。

perl -e 'while(<<>>){print}' /etc/passwd
perl -e 'while(<<>>){print}' "ls /tmp |"   # 報錯

這時想要從管道讀取數據,只能將管道放在perl命令的前面。

ls /tmp | perl -e 'while(<<>>){print}'
相關文章
相關標籤/搜索