本文是針對沒有Perl基礎,但想用perl一行式命令取代grep/awk/sed的人,用於速學Perl基礎知識。
Perl一行式系列文章:Perl一行式程序
perl的-e選項
perl命令的-e選項后可以書寫表達式,例如:
perl -e 'print "hello world\n"'
Perl中的函數調用經常可以省略括號,所以print "hello world\n"
表示的是print("hello world\n")
,但並非總是可以省略括號,一般來說只要不是自己定義的函數,都可以省略括號,除非少數出現歧義的情況。
在unix下建議使用單引號包圍perl -e
的表達式,在windows下建議使用雙引號包圍表達式。本文所有操作都是在Linux下操作的。
如果表達式中有多個語句,各語句之間使用分號";"隔開。例如:
perl -e 'print "hello";print "world"."\n"'
注意,上面使用了點號"."來連接兩個字符串。
稍后會解釋另一個表達式選項"-E",它和"-e"功能幾乎一樣,唯一不同的是"-E"會自動啟用一些高版本的功能。
print、printf、say和sprintf
Perl中的print函數不會自動換行,所以需要手動加上"\n"來換行。
perl -e 'print "hello world"'
perl -e 'print "hello world\n"'
Perl中的say()函數會自動換行,用法和print完全一致。但要使用say,需要使用use指定版本號高於5.010,。
$ perl -e 'use 5.010;say "hello world"'
hello world
使用"-E"選項替換"-e",可以省略版本的指定,因為"-E"自動啟用高版本功能。
$ perl -E 'say "hello world"'
hello world
Perl也支持printf函數,語法格式是printf "format_string",expr,...
。想必看本文的人都知道如何使用printf,所以只給個簡單示例。
$ perl -e 'printf "hello %s\n", "Perl"'
hello Perl
sprintf()函數表示按照printf的方式進行格式化,然后保存起來。保存起來的內容可以賦值給變量。
$ perl -e '$a = sprintf "hello %s\n", "Perl";print "$a"'
hello Perl
上面將格式化后的字符串helloPerl\n
保存到了變量$a
中,然后print輸出了變量"$a"。
變量
Perl中聲明變量x需要使用$x
。例如:
$x = "abc"
$y = 33
($x,$y)=("abc",33)
${var} = 333 # 加大括號是標准寫法
下面是一行式命令中的變量使用示例:
perl -e '$a="hello world";print "$a\n"'
Perl中變量賦值時,總是會先計算右邊的結果,再賦值給左邊的變量。所以,變量交換非常方便:
($var1,$var2)=($var2,$var1)
對於一行式Perl程序,變量可以無須事先聲明直接使用。
perl -e 'print $a + 1,"\n"'
如果想要判斷變量是否已經定義,可以使用defined($var)
。
perl -e 'if(defined($a)){print $a,"\n"}'
不帶任何修飾的變量是全局變量,如果想要定義為局部變量,可以在變量前加上my
。my是一個函數。
my $a = "abc"
對於Perl一行式程序來說,幾乎不需要考慮my。但及少數情況下需要使用大括號或其它語句塊的時候,可以使用my來聲明局部變量,出了語句塊范圍變量就失效。
$ perl -e '
$a = 33;
{
my $a = "abc";
print $a,"\n";
}
print $a,"\n";'
數值、字符串和反斜線序列
例如:
4 + 3 # 7
3.12 + 3.22 # 6.34
"4abc" + "3xyz" # 7
"abc" + "3xyz" # 3
"1.2abc" + "3.1x" # 4.3
1..6 # 1 2 3 4 5 6
1.2..6.4 # 1 2 3 4 5 6
需要解釋下上面的幾行語句。
- 數值和字符串之間的轉換取決於做什么運算。例如加法表示數學運算,會讓字符串轉換成數值
- 浮點數和整數之間的轉換仍然取決於所處的環境,在需要整數的時候浮點數會直接截斷成整數
字符串使用單引號或雙引號包圍,此外,Perl中也有反引號,這3種引用和shell中的單、雙、反引號類似。:
- 雙引號表示弱引用,變量可以在雙引號中進行內容替換
- 單引號表示強引用,內部不會進行變量替換,反斜線序列也會失效
- 在unix下的perl一行式程序中因為一般使用單引號包圍-e表達式,所以一行式perl中單引號比較少用
- 如果非要使用單引號,可以考慮使用q()來引用,見下文對q、qq和qx的解釋
- 反引號表示執行操作系統命令並取得命令的輸出結果,需要記住的是它自帶尾隨換行符(除非所執行命令就沒有帶尾隨換行)。例如
$a = `date +%F %T`
、$files = `ls /root`
$ perl -e '$a = `date +"%F %T"`;print $a'
2019-01-03 19:55:32
Perl中有以下幾個常見的反斜線序列:
\n
\r
\t
\l # 將下個字母轉換為小寫
\L # 將后面的多個字母都轉換為小寫,直到遇到\E
\u # 將下個字母轉換為大寫
\U # 將后面的多個字母都轉換為大寫,直到遇到\E
\Q # 和\E之間的所有字符都強制當作字面符號
\E # \L、\U和\Q的結束符號
字符串連接需要使用".",例如"abc"."def"
等價於"abcdef"
。字符串重復次數可以使用小寫字母x,例如"a" x 3
得到"aaa"
,"abc" x 2
得到abcabc
。
Perl中數值和字符、字符串都支持自增、自減操作。相關示例參見Perl中的自增、自減操作。
q、qq和qx
在Perl中,引號不一定非要寫成符號,可以使用q()來表示單引號、qq()來表示雙引號、qx()表示反引號。其中這里的括號可以替換成其它成對的符號,例如qq{}、qq//、qq%%
都是可以的。
使用q類型的引用可以避免在shell中一行式Perl程序的引號轉義問題。
例如,在一行式Perl中想要保留單引號:
$ perl -e "print q(abc'd)"
數組
Perl中的數組本質上是一個列表(對於Perl一行式命令,可以將列表等價於數組),要聲明數組,使用@
符號。
@arr = ("Perl", "Python", "Shell")
@{arr} = (1,2,3) # 加上大括號是標准的寫法
@empty = () # 空數組
書寫列表時,字符串需要使用引號包圍,逗號分隔各個元素。還可以使用qw()來寫列表,不需要再使用逗號分隔元素,而是使用空格,且每個元素都默認以單引號的方式包圍。所以下面是等價的:
@arr = qw(Perl Python Shell abc\ndef)
@arr = ('Perl','Python','Shell,'abc\ndef')
對於一行式的perl命令,變量和數組可以直接使用而無需事先聲明。
數組可以直接放在雙引號中輸出,默認輸出的時候是用空格分隔各元素。
$ perl -e '@arr=qw(Perl Python Shell);print "@arr\n"'
Perl Python Shell
要取數組中的某個元素,使用$
符號。第一個元素$arr[0]
,第二個元素$arr[1]
。例如:
$ perl -e '@arr=qw(Perl Python Shell);print "$arr[1]\n"'
Python
數組$#arr
或$#{arr}
表示數組的最后一個數組索引值,所以數組元素個數等於該值加1。
如果想要直接取得數組的個數,將數組賦值給一個變量或者使用scalar()函數即可。這涉及到Perl的上下文知識,不是本文內容,所以記住即可。
$ perl -e '
@arr = qw(Shell Perl PHP);
$num = @arr;print "$num\n";
print scalar @arr,"\n";'
數組的索引可以是負數,-1表示最后一個元素,-2表示倒數第二個元素。所以$arr[-1]
等價於$arr[$#arr]
,都是最后一個元素。
數組切片
數組支持切片功能,切片操作使用@
符號,切片操作會返回一個新的列表(數組)。切片時同一個元素可以出現多次,且順序隨意,這比其它語言的切片要自由的多。例如:
@arr = qw(Perl Python Shell Ruby PHP)
@arr[0] # 取第一個元素形成一個新列表
@arr[1,1,0,-2] # 取兩次第2個元素,一次第1個元素,一次倒數第2個元素形成新列表
@arr[1..3] # 以序列的方式取第2到第4個元素形成新列表
下面是一個示例:
$ perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
print "@arr[1,1,0,-2]\n"'
Python Python Perl Ruby
如果想要取從第2個到倒數第2個元素,可以使用這種切片方式@arr[1..$#{arr}-1]
。例如:
$ perl -e '@arr=qw(Perl Python Shell Ruby PHP);
print "@arr[1..$#{arr}-1]\n"'
Python Shell Ruby
操作數組相關函數
數組可以使用pop/push函數來移除、追加最尾部的一個元素,使用shift/unshift函數移除、插入首部第一個元素。如果想要操作中間某個元素,可以使用splice()函數。這些函數的用法參見:增、刪數組元素。
另外,還有sort()、reverse()函數,在Perl中sort太過強大,不適合在這里展開解釋,所以記住它可以用來排序列表(數組)做簡單使用即可。例如:
$ perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
@arr1 = sort @arr;
print "@arr1\n"'
PHP Perl Python Ruby Shell
對於sort還需注意的是,它不是在原地排序的,而是生成一個排序后的新列表,原數組中元素的順序並不會受排序的影響。所以需要將這個新列表賦值給另一個數組變量才能得到排序后的結果,正如上面的示例一樣。
但也有技巧可以直接輸出排序后的結果,而且這個技巧非常有用:
$ perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
print "@{ [ sort @arr ] }\n"'
PHP Perl Python Ruby Shell
這屬於Perl的高級技巧,這里大致解釋一下。它分成2個部分:
- 第一部分是
[]
,它表示構造一個匿名列表,匿名列表的內容可以來自於字面元素,也可以來自函數的結果或者表達式的結果,正如上面是將sort函數排序的結果構造成匿名列表; - 第二部分是
@{xxx}
,它表示將列表xxx引用進行解除,然后可以插入到雙引號中進行輸出。
所以,當想要將某個操作的結果直接輸出時,就可以采取這種方式:
@{ [ something you do ] }
遍歷數組
要遍歷數組,可以使用for、foreach、each,當然也可以使用while,只不過使用for/foreach/each要更方便。關於for/foreach/each/while詳細的內容,見后文。
# foreach遍歷數組
perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
foreach $i (@arr){print "$i\n"}'
# for遍歷數組:元素存在性測試遍歷法
perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
for $i (@arr){print "$i\n"}'
# for遍歷數組:索引遍歷法
perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
for($i=0; $i<=$#arr; $i++){print "$arr[$i]\n"}'
# each遍歷數組:key/value遍歷
perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
while (($index, $value) = each(@arr)){
print "$index: $value\n"
}'
必須注意的是,Perl中for/foreach以元素存在性測試遍歷時,控制變量$i
是各個元素的引用,而不是復制各個元素再賦值給$i
,所以在遍歷過程中修改$i
的值會直接修改原數組。
$ perl -e '
@arr=qw(Perl Python Shell Ruby PHP);
for $i (@arr){$i = $i."X"}; # 為每個元素加尾隨字符"X"
print "@arr\n"'
PerlX PythonX ShellX RubyX PHPX
split()和join()函數
join()用於將列表連接成字符串,split()用於將字符串分割成列表。
join $sep,$list
split /pattern_sep/,$string,limit
詳細用法和示例參見:Perl處理數據(一):s替換、split和join。下面是兩個簡單示例:
print join "--",a,b,c,d,e; # 輸出"a--b--c--d--e"
$str="abc:def::1234:xyz";
@list = split /:/,$str;
上面的字符串分割后將有5個元素:abc,def,空,1234,xyz。
hash(關聯數組)
Perl也支持hash數據結構,hash結構中key/value一一映射,和Shell中的關聯數組是一個概念。
在Perl中,無論是數組還是hash結構,本質都是列表。所以下面的列表數據可以認為是數組,也可以認為是hash,關鍵在於它賦值給什么類型。
("name","longshuai","age",23)
在Perl中,數組類型使用@
前綴表示,hash類型則使用%
前綴表示。
所以,下面的表示hash結構:
%Person = ("name","longshuai","age",23)
%Person = qw(name longshuai age 23)
列表作為hash結構時,每兩個元素組成一個key/value對,其中key部分必須是字符串類型。所以上面的hash結構表示的映射關系為:
%Person = (
name => "longshuai",
age => 23,
)
實際上,上面使用胖箭頭=>
的寫法在Perl中是允許且推薦的,它是元素分隔符逗號的另一種表示方式,且使用胖箭頭更能展現一一對應的關系。在使用胖箭頭的寫法時,如果key是符合命名規范的,可以省略key的引號(因為它是字符串,正常情況下是應該加引號包圍的),正如上面所寫的格式。
hash數據不能在雙引號中進行內容替換,可以直接輸出它,但直接輸出時hash的元素是緊挨在一起的,這表示hash的字符串化。
$ perl -e '
%hash = qw(name longshuai age 23);
print "%hash","\n"'
%hash
$ perl -e '
%hash = qw(name longshuai age 23);
print %hash,"\n"'
age23namelongshuai
從hash中取元素使用$
表示,如$hash{KEY}
,從hash中切片使用@
表示,如@hash{KEY1,KEY2}
這和數組是一樣的。雖然hash類型本身不能在雙引號中進行內容替換,但hash取值或者hash切片可以在雙引號中替換。
$ perl -e '
%hash = (name=>"longshuai",age=>23);
print "$hash{name}","\n"'
longshuai
$ perl -e '
%hash = (name=>"longshuai",age=>23);
print "@hash{name,name,age,age}","\n"'
longshuai longshuai 23 23
hash相關函數
主要有keys()、values()、exists()和delete()。
- keys()返回hash結構中所有key組成的列表。例如
keys %hash
- values()則返回hash結構中所有value組成的列表。例如
values %hash
- exists()用來檢測hash結構中元素是否存在。例如
exists $hash{KEY}
- delete()用來刪除hash中的一個元素。例如
delete $hash{KEY}
下面是keys和values函數的示例。
$ perl -e '
%hash = (name=>"longshuai",age=>23);
print "keys:\n";
print keys %hash,"\n";
print "values:\n";
print values %hash,"\n";'
keys:
agename
values:
23longshuai
看起來不是很爽,所以賦值給數組再來輸出。
$ perl -e '
%hash = (name=>"longshuai",age=>23);
@keys = keys %hash;
@values = values %hash;
print "=========\n";
print "@keys\n";
print "=========\n";
print "@values\n";'
=========
age name
=========
23 longshuai
如何知道hash結構中有多少個key/value對?是否記得將數組(列表)賦值給一個變量時,得到的就是它的個數?
$ perl -e '
%hash = (name=>"longshuai",age=>23);
$nums = keys %hash;
print "$nums\n";'
2
如果想要直接輸出個數而不是先賦值給變量,可以對一個列表使用函數scalar(),它會強制讓Perl將列表當作標量(變量):
$ perl -e '
%hash = (name=>"longshuai",age=>23);
print scalar keys %hash,"\n";'
2
如何排序hash結構?只需對keys的結果應用sort/reverse函數,再進行遍歷輸出即可。
$ perl -e '
%hash = (name=>"longshuai",age=>23);
for $key (sort keys %hash){
print "$key => $hash{$key}\n";
}'
age => 23
name => longshuai
遍歷hash
要遍歷hash結構,可以使用while + each或者for/foreach遍歷hash的key或value。
# 使用while + each
$ perl -e '
%hash = (name=>"longshuai",age=>23);
while(($key,$value) = each %hash){
print "$key => $value","\n"
}'
name => longshuai
age => 23
# 使用for或foreach去遍歷keys或values
$ perl -e '
%hash = (name=>"longshuai",age=>23);
for $key (keys %hash){
print "$key => $hash{$key}","\n"
}'
age => 23
name => longshuai
默認變量$_、@ARGV、@_
在Perl中,有一個非常特殊的變量$_
,它表示默認變量。
當沒有為函數指定參數時、表達式中需要變量但卻沒給定時都會使用這個默認變量$_
。
例如:
perl -e '$_="abc\n";print'
print函數沒有給參數,所以默認輸出$_
的內容。
再例如,for/foreach的遍歷行為:
for $i (@arr){print "$i\n"}
for (@arr){print "$_\n"}
for本該需要一個控制變量指向@arr
中每個元素,但如果沒有給定,則使用默認變量$_
作為控制變量。
用到默認變量的地方很多,沒法列出所有使用$_
的情況。所以簡單地總結是:只要某些操作需要參數但卻沒有給定的時候,就可以使用$_
。
$_
是對於標量變量而言的默認變量。對於需要列表/數組的時候,默認變量不再是$_
,而是@ARGV
或@_
:在自定義子程序(函數)內部,默認變量是@_
,在自定義子程序外部,默認變量是@ARGV
。例如:
$ perl -e '
$name = shift;
$age = shift;
print "$name:$age\n"' longshuai 23
默認數組(列表)變量對一行式perl命令來說可能遇不上,所以了解一下足以,只要能在需要的時候看懂即可。
布爾值判斷
在Perl中,真假的判斷很簡單:
- 數值0為假,其它所有數值為真
- 字符串空""為假,字符串"0"為假,其它所有字符串為真(例如"00"為真)
- 空列表、空數組、undef、未定義的變量、數組等為假
注意,Perl中沒有true和false的關鍵字,如果強制寫true/false,它們可能會被當作字符串,而字符串為真,所以它們都表示真。例如下面的兩行,if的測試對象都認為是字符串而返回真。
perl -e "if(true){print "aaaa\n"}"
perl -e "if(false){print "aaaa\n"}"
大小比較操作
Perl的比較操作符和bash完全相反。數值比較采用符號,字符串比較采用字母。
數值 字符串 意義
-----------------------------
== eq 相等
!= ne 不等
< lt 小於
> gt 大於
<= le 小於或等於
>= ge 大於或等於
<=> cmp 返回值-1/0/1
最后一個<=>
和cmp
用於比較兩邊的數值/字符串並返回狀態碼-1/0/1:
- 小於則返回-1
- 等於則返回0
- 大於則返回1
對於<=>
,如果比較的雙方有一方不是數值,該操作符將返回undef。
幾個示例:
35 != 30 + 5 # false
35 == 35.0 # true
'35' eq '35.0' # false(str compare)
'fred' lt 'bay' # false
'fred' lt 'free' # true
'red' eq 'red' # true
'red' eq 'Red' # false
' ' gt '' # true
10<=>20 # -1
20<=>20 # 0
30<=>20 # 1
邏輯運算
Perl支持邏輯與(and &&
)、邏輯或(or ||
)、邏輯非(not !
)運算,還支持一個額外的//
操作。它們都會短路計算。
符號類型的邏輯操作&& || ! //
優先級很高,為了保險,符號兩邊的表達式應當使用括號包圍。關鍵字類型的邏輯操作優先級很低,不需要使用括號包圍。
($a > $b) && ($a < $c)
$a > $b and $a < $c
Perl的短路計算非常特別,它返回的是最后運算的表達式的值。也就是說,它有返回值,通過返回值可以判斷短路計算的布爾邏輯是真還是假。
- 如果這個返回值對應的布爾值為真,則整個短路計算自然為真
- 如果這個返回值對應的布爾值為假,則整個短路計算自然為假
所以,這個返回值既保證短路計算的結果不改變,又能得到返回值。這個返回值有時候很有用,例如,可以通過邏輯或的操作來設置默認值:
$name = $myname || "malongshuai"
上面的語句中,如果$myname
為真,則$name
被賦值為$myname
,如果$myname
為假,則賦值為"malongshuai"。
但上面有一種特殊的情況,如果$myname
已經定義了,且其值為數值0或字符串"0",它也返回假。
這和預期有所沖突,這時可以使用//
來替代||
。//
表示只要左邊的內容已經定義了就返回真,而無論左邊的內容代表的布爾值是真還是假。
$name = $myname // "malongshuai"
所以,就算$myname
的值為0,$name
也會賦值為0而不是"malongshuai"。
流程控制
if、unless和三目運算邏輯
語法格式:
# if語句
if(TEST){
...
} elsif {
...
} else{
...
}
# unless語句
unless(TEST){
...
}
# 三目運算符
expression ? if_true : if_false
if表示TEST為真的時候,執行后面的語句塊,否則執行elsif或else語句塊。
unless則相反,TEST為假的時候,執行后面的語句塊。也就是等價於if(!TEST)
。unless也有else/elsif塊,但基本不會用,因為可以轉換成if語句。
注意TEST部分,只要它的結果在布爾邏輯上是真,就表示真。例如:
if("abc"){} # 真
if(0){} # 假
if(0 > 1){} # 假
where和until循環
語法:
while(CONDITION){
commands;
}
until(CONDITION){
commands;
}
Perl的until和其它某些語言的until循環有所不同,Perl的until循環,內部的commands主體可能一次也不會執行,因為Perl會先進行條件判斷,當條件為假時就執行,如果第一次判斷就為真,則直接退出until。
for和foreach循環
Perl中的for循環和Bash Shell類似,都支持兩種風格的for:
# (1).C語言風格的for
for(expr1;expr2;expr3){...}
for($i=0;$i<100;$i++){...}
# (2).元素存在性測試的for
for $i (LIST){...}
# (3).元素存在性測試的for等價於foreach
foreach $i (LIST){...}
對於第一種for語法(即C語言風格的for),3個部分都可以省略,但分號不能省略:
- 省略第一部分expr1表示不做初始化
- 省略第二部分expr2表示不設置條件,意味着可能會無限循環
- 省略第三部分expr3表示不做任何操作,可能也會無限循環
例如下面將3個部分全都省略,將會無限循環:
for(;;){
print "never stop";
}
對於(2)和(3)的元素存在性測試的循環,用來迭代遍歷列表,每次迭代過程中通過控制遍歷$i
引用當前被迭代的元素。注意:
$i
是引用被迭代的元素,而不是復制列表中被迭代的元素然后賦值給$i
,這意味着迭代過程中修改$i
會直接影響元素列表數據$i
可以省略,省略時使用默認變量$_
$i
在遍歷結束后,會恢復為遍歷開始之前的值
例如:
$ perl -e '
@arr = qw(Perl Shell Python Ruby PHP);
for (@arr){
$_ = $_."x";
}
print "@arr\n"'
Perlx Shellx Pythonx Rubyx PHPx
上面沒有顯式指定控制變量,所以采用默認變量$_
。且在迭代過程中為每個$_
都連接了一個尾隨字符"x",它會直接修改元素數組。
each遍歷
each用來遍歷hash或數組,每次迭代的過程中,都獲取hash的key和value,數組的index(數值,從0開始)和元素值。
遍歷hash:
#!/usr/bin/perl -w
use strict;
my %hash = (
name1 => "longshuai",
name2 => "wugui",
name3 => "xiaofang",
name4 => "woniu",
);
while(my($key,$value) = each %hash){
print "$key => $value\n";
}
輸出結果:
name4 => woniu
name3 => xiaofang
name2 => wugui
name1 => longshuai
遍歷數組:
#!/usr/bin/perl -w
use strict;
my @arr = qw(Perl Shell Python PHP Ruby Rust);
while(my($key,$value) = each @arr){
print "$key => $value\n";
}
輸出結果:
0 => Perl
1 => Shell
2 => Python
3 => PHP
4 => Ruby
5 => Rust
表達式形式的流程控制語句
Perl支持單表達式后面加流程控制符。如下:
command OPERATOR CONDITION;
例如:
print "true.\n" if $m > $n;
print "true.\n" unless $m > $n;
print "true.\n" while $m > $n;
print "true.\n" until $m > $n;
print "$_\n" for @arr;
print "$_\n" foreach @arr;
書寫時,很多時候會分行並縮進控制符:
print "true.\n" # 注意沒有分號結尾
if $m > $n;
改寫的方式幾個注意點:
- 控制符左邊只能用一個命令。除非使用do語句塊,參見下面解釋的do語句塊
- for/foreach的時候,不能自定義控制變量,只能使用默認的$_
- while或until循環的時候,因為要退出循環,只能將退出循環的條件放進前面的命令中
- 例如:
print "abc",($n += 2) while $n < 10;
print "abc",($n += 2) until $n > 10;
- 例如:
改寫的方式不能滿足需求時,可以使用普通的流程結構。
大括號:運行一次的語句塊
使用大括號包圍一段語句,這些語句就屬於這個語句塊。這個語句塊其實是一個循環塊結構,只不過它只循環一次。語句塊有自己的范圍,例如可以將變量定義為局部變量。
$ perl -e '
$a = 33;
{
my $a = "abc";
print $a,"\n";
}
print $a,"\n";'
do語句塊
do語句塊結構如下:
do {...}
do語句塊像是匿名函數一樣,沒有名稱,給定一個語句塊,直接執行。do語句塊的返回值是最后一個執行的語句的返回值。
例如,if-elsif-else分支結構賦值的語句:
if($gender eq "male"){
$name="Malongshuai";
} elsif ($gender eq "female"){
$name="Gaoxiaofang";
} else {
$name="RenYao";
}
改寫成do語句塊:
$name=do{
if($gender eq "male"){"Malongshuai"}
elsif($gender eq "female") {"Gaoxiaofang"}
else {"RenYao"}
}; # 注意結尾的分號
前面說過,表達式形式的流程控制語句控制符左邊只能是一個命令。例如:
print $_+1,"\n";print $_+2,"\n" if $_>3;
# 等價於下面兩條語句:
print $_+1,"\n";
print $_+2,"\n" if $_>3;
使用do語句塊,可以將多個語句組合並當作一個語句。例如:
do{print $a+1,"\n";print $a+2,"\n"} if $a>3;
do{}
中有自己的作用域范圍,可以聲明屬於自己范圍內的局部變量。
不要把do和大括號搞混了,大括號是被解釋的語句塊范圍的語法符號,可以用來標記自己的作用域范圍。但do{}
是語句,是被執行的語句塊,也有自己的作用域范圍。
last/next/redo/continue
- last相當於其它語言里的break關鍵字,用於退出當前循環塊
- (for/foreach/while/until/執行一次的語句塊都屬於循環塊),注意是只退出當前層次的循環,不會退出外層循環
- next相當於其它語言里的continue關鍵字,用於跳入下一次迭代。同樣只作用於當前層次的循環
- redo用於跳轉到當前循環層次的頂端,所以本次迭代中曾執行過的語句可能會再次執行
- continue表示每輪循環的主體執行完之后,都執行另一段代碼
熟悉sed的人肯定很容易理解這里redo和continue的作用。sed默認情況下會輸出每一行被處理后的內容,這是因為它有一個和這里continue一樣的邏輯,在perl命令的"-p"選項也一樣,到時候會解釋這個continue的邏輯。sed的"-D"選項則是處理后立即回到sed表達式的頂端再次對模式空間進行處理,直到模式空間沒有內容,這實現的是redo的邏輯。
BEGIN/END語句塊
Perl像awk一樣,也有BEGIN和END語句塊,功能和awk是一樣的。實際上Perl除了BEGIN/END,還有CHECK、INIT和UNITCHECK語句塊,不過對於一行式Perl程序,BEGIN/END就足夠了。
Perl命令行參數和ARGV
perl命令行的參數存放在數組ARGV(@ARGV)中,所以可以訪問$ARGV[n]
、遍歷,甚至修改命令行參數。
$ perl -e 'print "@ARGV\n"' first second
first second
不難看出,@ARGV
數組是從-e表達式之后才開始收集的。
其實ARGV數組有點特別,如果參數中有被讀取的文件參數,那么每開始讀一個文件,這個文件就從ARGV數組中剔除。所以,在程序編譯期間(BEGIN語句塊),ARGV數組中包含了完整的參數列表,處理第一個參數文件時,ARGV數組中包含了除此文件之外的其它參數列表,處理第二個參數文件時,ARGV數組中繼續剔除這個文件參數。
例如,perl一行式命令中,"-p"選項會輸出參數文件的每一行被處理后的數據,也就是說它會讀取參數文件。
$ echo aaaa > a.txt
$ echo bbbb > b.txt
$ perl -pe '
BEGIN{
print "in BEGIN:\n";
print "@ARGV\n"
}
print "in argv file: @ARGV\n";
END{
print "in END:\n";
print "@ARGV\n"
}
' a.txt b.txt
輸出結果:
in BEGIN:
a.txt b.txt
in argv file: b.txt
aaaa
in argv file:
bbbb
in END:
其實,除了ARGV數組@ARGV
,還有幾個相關的ARGV:
$ARGV
:該變量表示當前正在被讀取的參數文件ARGV
:是一個預定義的文件句柄,只對<>
有效,表示當前正被<>
讀取的文件句柄ARGVOUT
:也是一個預定義的文件句柄,表示當前正打開用於輸出的文件句柄,比較常和一行式Perl程序的-i選項結合使用
例如:
$ echo aaaa > a.txt
$ echo bbbb > b.txt
$ perl -pe '
BEGIN{
print "in BEGIN:\n";
print "@ARGV\n"
}
print "reading me: $ARGV\n";
' a.txt b.txt
輸出結果:
in BEGIN:
a.txt b.txt
reading me: a.txt
aaaa
reading me: b.txt
bbbb
和shell交互:執行shell命令
perl可以直接執行shell中的命令方式有3種,但這里只介紹兩種:反引號(或qx)、system()函數。
反引號
反引號`COMMAND`
或 qx(COMMAND)
的方式是執行shell命令COMMAND后,將COMMAND的輸出結果保存下來作為返回值,它可以賦值給變量,也可以插入到某個地方。就像shell中的反引號是一樣的。
perl -e '$datetime = qx(date +"%F %T");print $datetime'
需要注意的是,反引號執行的結果中一般都會保留尾隨換行符(除非像printf一樣明確不給尾隨換行符),所以在print這些變量的時候,可以不用指定"\n"。或者為了保持同意,使用chomp()或chop()函數操作變量,去掉最后一個字符。
$ perl -e '
$datetime = qx(date +"%F %T");
chop $datetime; # 或chomp $datetime
print "$datetime\n";'
還可以更簡便一些,直接在賦值語句上chop或chomp:
$ perl -e '
chop($datetime = qx(date +"%F %T"));
print "$datetime\n";'
system()
第二種和shell交互的方式是system()函數。它會直接輸出所執行的命令,而不是將命令的結果作為返回值。所以,system()函數不應該賦值給變量。
system()要執行的命令部分需要使用引號包圍。而對於一行式perl程序來說,直接使用引號是一個難題,所以可以考慮使用qq()、q()的方式。例如:
$ perl -e '
system q(date +"%F %T");'
2019-01-03 20:18:34
如果一定想要將system()的結果賦值給變量,得到的賦值結果是system()的返回值,而它的返回值表示的是命令是否成功調用(嚴格地說是wait()的返回值)。
$ perl -e '
$datetime = system q(date +"%F %T");
print "$datetime\n";'
2019-01-03 20:23:21
0
還可以使用shell的管道、重定向等功能:
$myname="Malongshuai";
system "echo $myname >/tmp/a.txt";
system "cat <1.pl";
system 'find . -type f -name "*.pl" -print0 | xargs -0 -i ls -l {}';
system 'sleep 30 &';
system()的用法其實很復雜,如果上面簡單使用單引號包圍無法解決問題時,可以參考Perl和操作系統交互(一):system、exec和反引號,這里面對system()的參數做了非常透徹的分析。
和shell交互:向perl命令行傳遞來自shell的數據
對於一行式命令,可能會想要將shell的變量、shell中命令的執行結果通過變量傳遞給perl命令。本人收集了幾種方式,也許不全,但應該足夠應付所有情況。
方式一:通過環境變量$ENV{VAR}
perl程序在運行時,會自動注冊一個hash結構%ENV
,它收集來自shell的環境變量。例如讀取shell的PATH環境變量、查看當前所使用的SHELL。
$ perl -e 'print "$ENV{PATH}\n"'
$ perl -e 'print "$ENV{SHELL}\n"'
所以,想要獲取shell的變量,可以先將其導出為環境變量,再從%ENV
中獲取。
$ export name="longshuai"
$ perl -e 'print "$ENV{name}\n"'
方式二:將perl -e 'xxxx'的單引號拆開重組,直接將需要被shell解析的東西暴露給shell去解釋
$ name=longshuai
$ perl -e 'print "'$name'\n"'
longshuai
上面分成三部分'print "'
是一部分,$name
是一部分,它沒有被任何引號包圍,所以直接暴露給shell進行變量替換,'\n"'
是最后一部分。
這種方式需要對shell的引號解析非常熟練,對sed來說這種寫法有時候是必要的,因為這是sed和shell交互的唯一方式,但對於perl命令行來說,沒有必要這樣寫,因為可讀性太差,且很難寫,一般人真的不容易寫不來。
方式三:將變量放入參數位置,使其收集到ARGV數組中,然后在perl表達式中shift這些數據
$ name=longshuai
$ age=23
$ perl -e '
$name = shift;
$age = shift;
print "$name,$age\n"' $name $age
longshuai,23
注意上面的shift沒有給定參數,所以shift會使用默認變量。由於shift期待的操作對象是一個數組(列表),且不是在子程序內部(子程序內部表示在sub語句塊內),所以使用默認數組@ARGV
。也就說上面的代碼等價於:
$ perl -e '
$name = shift @ARGV;
$age = shift @ARGV;
print "$name,$age\n"' $name $age
方式四:使用perl -s選項
perl的-s選項允許解析--
之后的-xxx=yyy
格式的開關選項。
$ perl -e 'xxxxx' -- -name=abc -age=23 a.txt b.txt
從--
之后的參數,它們本該會被收集到ARGV中,但如果開啟了-s選項,這些參數部分如果是-xxx=yyy
格式的,則被解析成perl的變量$xxx
並賦值為yyy,而那些不是-xxx=yyy
格式的參數則被收集到ARGV數組中。
例如:
$ perl -se '
print "ARGV: @ARGV\n";
print "NAME & AGE: $name,$age\n";
' -- -name="longshuai" -age=23 abc def
ARGV: abc def
NAME & AGE: longshuai,23
上面傳遞的參數是-name="longshuai" -age=23 abc def
,因為開啟了-s選項,所以解析了兩個perl變量$name=longshuai $age=23
,另外兩個abc def
則被收集到ARGV數組中。
方式五:杜絕使用shell,完全替代為perl。在perl中反引號或qx()也支持運行shell程序
例如:
$ name=longshuai
$ perl -e '
$name = qx(echo \$name);
print "name in perl: $name"'
name in perl: longshuai
$ perl -e '
$time = qx(date +"%T");
print "time in perl: $time"'
time in perl: 19:52:39
注意上面qx(echo \$name)
的反斜線不可少,否則$name
會被perl解析,轉義后才可以保留$符號被shell解析。
Perl讀寫文件
一行式perl程序的重頭戲自然是處理文本數據,所以有必要解釋下Perl是如何讀、寫數據的。
讀取標准輸入<STDIN>
<STDIN>
符號表示從標准輸入中讀取內容,如果沒有,則等待輸入。<STDIN>
讀取到的結果中,一般來說都會自帶換行符(除非發生意外,或EOF異常)。
$ perl -e '$name=<STDIN>;print "your name is: $name";'
longshuai # 這是我輸入的內容
your name is: longshuai # 這是輸出的內容
因為讀取的是標准輸入,所以來源可以是shell的管道、輸入重定向等等。例如:
$ echo "longshuai" | perl -e '$name=<STDIN>;print "yourname is: $name";'
your name is: longshuai
在比如,判斷行是否為空行。
$ perl -e '
$line=<STDIN>;
if($line eq "\n"){
print "blank line\n";
} else {
print "not blank: $line"
}'
注意,<STDIN>
每次只讀取一行,遇到換行符就結束此次讀取。使用while循環可以繼續向后讀取,或者將其放在一個需要列表的地方,也會一次性讀取所有內容保存到列表中。
# 每次只讀一行
$ echo -e "abc\ndef" | perl -e '$line=<STDIN>;print $line;'
abc
# 每次迭代一行
$ echo -e "abc\ndef" | perl -e 'while(<STDIN>){print}'
abc
def
# 一次性讀取所有行保存在一個列表中
$ echo -e "abc\ndef" | perl -e 'print <STDIN>'
abc
def
另外需要注意的是,如果將<STDIN>
顯示保存在一個數組中,因為輸出雙引號包圍的數組各元素是自帶空格分隔的,所以換行符會和空格合並導致不整齊。
$ echo -e "abc\ndef" | perl -e '@arr=<STDIN>;print "@arr"'
abc
def
解決辦法是執行一下chomp()或chop()操作,然后在輸出時手動指定換行符。
$ echo -e "abc\ndef" | perl -e '@arr=<STDIN>;chomp @a;print "@arr\n"'
abc def
讀取文件輸入<>
使用兩個尖括號符號<>
表示讀取來自文件的輸入,例如從命令行參數@ARGV
中傳遞文件作為輸入源。這個符號被稱為"鑽石操作符"。
例如讀取並輸出/etc/passwd和/etc/shadow文件,只需將它們放在表達式的后面即可:
$ perl -e '
while(<>){
print $_; # 使用默認變量
}' /etc/passwd
可以直接在perl程序中指定ARGV數組讓<>
讀取,是否還記得$ARGV
變量表示的是當前正在讀取的文件?
perl -e '@ARGV=qw(/etc/passwd);while(<>){print}'
一般來說,只需要while(<>)
中一次次的讀完所有行就可以。但有時候會想要在循環內部繼續讀取下一行,就像sed的n和N命令、awk的next命令一樣。這時可以單獨在循環內部使用<>
,表示繼續讀取一行。
例如,下面的代碼表示讀取每一行,但如果行首以字符串abc開頭,則馬上讀取下一行。
perl -e '
while(<>){
if($_ =~ /^abc/){
<>;
}
...
}'
文件句柄
其實<>
和<STDIN>
是兩個特殊的文件讀取方式。如果想要以最普通的方式讀取文件,需要打開文件句柄,然后使用<FH>
讀取對應文件的數據。
打開文件以供讀取的方式為:
open FH, "<", "/tmp/a.log"
open $fh, "/tmp/a.log" # 與上面等價
文件句柄名一般使用大寫字母(如FH)或者直接使用變量(如$fh)。
然后讀取即可:
while(<FH>){...}
while(<$fh>){...}
例如:
$ perl -e 'open FH,"/etc/passwd";while(<FH>){print}'
除了打開文件句柄以供讀取,還可以打開文件句柄以供寫入:
open FH1,">","/tmp/a.log"; # 以覆蓋寫入的方式打開文件/tmp/a.log
open FH2,">>","/tmp/a.log"; # 以追加寫入的方式打開文件/tmp/a.log
要寫入數據到文件,直接使用print、say、printf即可,只不過需要在這些函數的第一個參數位上指定輸出的目標。默認目標為STDOUT,也就是標准輸出。
例如:
print FH1,"hello world\n";
say FH,"hello world\n";
printf FH1,"hello world\n";
在向文件句柄寫入數據時,如果使用的是變量形式的文件句柄,那么print/say/printf可能會無法區分這個變量是文件句柄還是要輸出的內容。所以,應當使用{$fh}
的形式避免歧義。
print {$fh},"hello world\n";
正則表達式匹配和替換
本文通篇都不會深入解釋Perl正則,因為內容太多了,而且我已經寫好了一篇從0基礎到深入掌握Perl正則的文章Perl正則表達式超詳細教程以及s///
替換的文章Perl的s替換命令。
對於學習perl一行式程序來說,無需專門去學習Perl正則,會基礎正則和擴展正則足以。雖不用專門學Perl正則,但有必要知道Perl的正則是如何書寫的。
使用str =~ m/reg/
符號表示要用右邊的正則表達式對左邊的數據進行匹配。正則表達式的書寫方式為m//
或s///
,前者表示匹配,后者表示替換。關於m//
或s///
,其中斜線可以替換為其它符號,規則如下:
- 雙斜線可以替換為任意其它對應符號,例如對稱的括號類,
m()
、m{}
、s()()
、s{}{}
、s<><>
、s[][]
,相同的標點類,m!!
,m%%
、s!!!
、s###
、s%%%
等等 - 只有當m模式采用雙斜線的時候,可以省略m字母,即
//
等價於m//
- 如果正則表達式中出現了和分隔符相同的字符,可以轉義表達式中的符號,但更建議換分隔符,例如
/http:\/\//
轉換成m%http://%
所以要匹配/替換內容,有以下兩種方式:
- 方式一:使用
data =~ m/reg/
、data =~ s/reg/rep/
,可以明確指定要對data對應的內容進行正則匹配或替換 - 方式二:直接
/reg/
、s/reg/rep/
,因為省略了參數,所以使用默認參數變量,它等價於$_ =~ m/reg/
、$_ =~ s/reg/rep/
,也就是對$_
保存的內容進行正則匹配/替換
匹配
Perl中匹配操作返回的是匹配成功與否,成功則返回真,匹配不成功則返回假。當然,Perl提供了特殊變量允許訪問匹配到的內容,甚至匹配內容之前的數據、匹配內容之后的數據都提供了相關變量以便訪問。見下面的示例。
例如:
1.匹配給定字符串內容
$ perl -e '
$name = "hello gaoxiaofang";
if ($name =~ m/gao/){
print "matched\n";
}'
或者,直接將字符串拿來匹配:
"hello gaoxiaofang" =~ m/gao/;
2.匹配來自管道的每一行內容,匹配成功的行則輸出
while (<STDIN>){
chomp;
print "$_ was matched 'gao'\n" if /gao/;
}
上面使用了默認的參數變量$_
,它表示while迭代的每一行數據;上面還簡寫正則匹配方式/gao/
,它等價於$_ =~ m/gao/
。
以下是執行結果:
$ echo -e "malongshuai\ngaoxiaofang" | perl -e "
while (<STDIN>){
chomp;
print qq(\$_ was matched 'gao'\n) if /gao/;
}"
gaoxiaofang was matched 'gao'
3.匹配文件中每行數據
while (<>){
chomp;
if(/gao/){
print "$_ was matched 'gao'\n";
}
}
替換
s///
替換操作是原地生效的,會直接影響原始數據。
$str = "ma xiaofang or ma longshuai";
$str =~ s/ma/gao/g;
print "$str\n";
s///
的返回值是替換成功的次數,沒有替換成功返回值為0。所以,s///
自身可以當作布爾值進行判斷,如
if(s/reg/rep/){
print "$_\n";
}
print "$_\n" if s/reg/rep/
while(s/reg/rep/){
...
}
如果想要直接輸出替換后得到的字符串,可以加上r
修飾符,這時它不再返回替換成功的次數,而是直接返回替換后的數據:
$ perl -e '
$str = "ma xiaofang or ma longshuai";
print $str =~ s/ma/gao/gr,"\n";'
gao xiaofang or gao longshuai