讀取標准輸入<STDIN>
<STDIN>
表示從標准輸入中讀取內容,如果沒有,則等待輸入。<STDIN>
讀取到的結果中,如果沒有意外,都會自帶換行符。
例如,test.plx文件內容:
#!/usr/bin/perl
#
$line=<STDIN>;
if($line eq "\n"){
print "blank line\n";
} else {
print "not blank: $line"
}
注意上面的else語句中,$line
后面沒有加換行符,因為<STDIN>
自帶換行符。
下面的命令,將等待輸入和回車。如果直接回車,則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>
:
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}'