Perl基礎速成

本文是針對沒有Perl基礎,但想用perl一行式命令取代grep/awk/sed的人,用於速學Perl基礎知識。html

Perl一行式系列文章:Perl一行式程序正則表達式

perl的-e選項

perl命令的-e選項後能夠書寫表達式,例如:shell

perl -e 'print "hello world\n"'

Perl中的函數調用常常能夠省略括號,因此print "hello world\n"表示的是print("hello world\n"),但並不是老是能夠省略括號,通常來講只要不是本身定義的函數,均可以省略括號,除非少數出現歧義的狀況。express

在unix下建議使用單引號包圍perl -e的表達式,在windows下建議使用雙引號包圍表達式。本文全部操做都是在Linux下操做的。windows

若是表達式中有多個語句,各語句之間使用分號";"隔開。例如:數組

perl -e 'print "hello";print "world"."\n"'

注意,上面使用了點號"."來鏈接兩個字符串。bash

稍後會解釋另外一個表達式選項"-E",它和"-e"功能幾乎同樣,惟一不一樣的是"-E"會自動啓用一些高版本的功能。數據結構

print、printf、say和sprintf

Perl中的print函數不會自動換行,因此須要手動加上"\n"來換行less

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

須要解釋下上面的幾行語句。

  1. 數值和字符串之間的轉換取決於作什麼運算。例如加法表示數學運算,會讓字符串轉換成數值
  2. 浮點數和整數之間的轉換仍然取決於所處的環境,在須要整數的時候浮點數會直接截斷成整數

字符串使用單引號或雙引號包圍,此外,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中,真假的判斷很簡單:

  1. 數值0爲假,其它全部數值爲真
  2. 字符串空""爲假,字符串"0"爲假,其它全部字符串爲真(例如"00"爲真)
  3. 空列表、空數組、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個部分均可以省略,但分號不能省略:

  1. 省略第一部分expr1表示不作初始化
  2. 省略第二部分expr2表示不設置條件,意味着可能會無限循環
  3. 省略第三部分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
相關文章
相關標籤/搜索