Perl 基本語法


最近在研究 openSUSE 的 OBS (編譯系統),其中很多部分用到 Perl。而且自己也感到有必要學點 Perl,當有一點其他程序語言的基礎,再學一門語言,入門還是非常簡單的。Perl 在 GNU/Linux 上應用如此廣泛,很多地方替換 shell 腳本會更靈活優秀高效,學習它也很有必要。本文是學習時的筆記,多是語法使用示例,沒有什么說明。擬研究 OBS 時,依據某個應用再寫一篇 Perl 應用教程。

標量

標量是 Perl 中最簡單的數據類型。大多數的標量是數字(如 255 或 3.25e20)或者字符串(如 hello或者蓋茨堡地址)。

數字

perl中所有數字內部的格式都是雙精度浮點數。

浮點數

1.25
255.000
255.0
7.25e45 #7.25x10 的 45 次方(一個大整數)
-6.5e24 # -6.5x10 的 24 次方(一個大的負數)
-12e-24 #- -12x10 的-24 次方(很小的負數)
-1.2E-23 #指數符號可以大寫(E)

整數

0
2001
-40
255
61298040283768

其中 61298040283768 也可以寫作:

61_298_040_283_768

非十進制整數

0377 #八進制數字 377,等同於十進制數字 255
0xff #十六進制數字 FF,等同於十進制數字 255
0b11111111 #等同於十進制數字 255

可以用下划線表示:

0x1377_0B77
0x50_65_72_7C

數字操作符

2+3 #2+3,5
5.1-2.4 #5.1-2.4,2.7
3*12 #3*12,36
14/2 #14/2,7
10.2/0.3 #10.2/0.3,34
10/3 #通常是浮點除,3.33333... ...

字符串

單引號字符串

'fred' #四個字符:f,r,e,d
'' #空字符(沒有字符)
'hello\n'
'\'\\' #單引號(')跟着反斜線(\)字符串

單引號中的 "\n" 不會被當作換行符來處理。

雙引號字符串

"barney" #等同於 'barney'
"hello world\n" #hello world,換行

字符串操作符

鏈接操作符 "."
"hello"."world" # 同於 "helloworld"
"hello".''."world" # 同於 "hello world"
'hello world'."\n" # 同於 "hello world\n"
重復操作符 "x"
"fred" x 3 # "fredfredfred"
5 x 4 # 等於 "5" x 4, "5555"

數字和字符串之間的自動轉換

大多數情況下,Perl 將在需要的時候自動在數字和字符串之間轉換。它怎樣知道什么時候需要字符串,什么時候需要數字呢?這完全依賴於標量值之間的的操作符。如果操作符(如+)需要數字,Perl 將把操作數當作數字看待。如果操作符需要字符串(如 . ), Perl 將把操作數當作字符串看待。不必擔心數字和字符串的區別;使用恰當的操作符,Perl 將為你做剩下的事。

"12" * "3" # * 操作符需要數字,所以結果為 36
"12fred34" * " 3" # 結果仍然是 36 , 后面的非數字部分和前面的空格都過濾掉。
"Z" . 5 * 7 # 等於 "Z".35,或 "Z35"

Perl 內嵌的警告

使用 -w 參數可以打開警告:

$ perl -w perl程序 # 命令行執行警告
#!/usr/bin/perl -w # 源代碼中使用警告

標量變量

標量變量可以存放一個標量值。標量變量的名字由一個美圓符號($)后接 Perl 標識符:由字母或下划線開頭,后接字母,數字,或者下划線。或者說由字母,數字和下划線組成,但不能由數字開頭。大小寫是嚴格區分的:變量$Fred 和變量$fred是不同的。任意字母,數字,下划線都有意義,如:

$a_very_long_variable_that_ends_in_1
$a_very_long_variable_that_ends_in_2

標量賦值

$fred = 17;
$barney = "hello";
$barney = $fred + 3;# 將$fred 的值加上三賦給$barney (20)
$barney= $barney*2;#將變量$barney 乘 2 再賦給$barney (40)

二元賦值操作符

$fred = $fred + 5; #沒有用二元賦值操作符
$fred+=5; #利用二元賦值操作符
$barney = $barney*3;
$barney*=3;
$str = str . ""; #$str 后接空格;
$str .= ""; #同上

print 輸出

print "hello world\n"; #輸出 hello world,后接換行符
print "The answer is", 6*7, ".\n"

字符串中引用標量變量

$meal = "brontosaurus steak" ;
$barney = "fred ate a $meal";
$barney = 'fred ate a'.$meal; # 同上

if 控制結構

if ($name gt 'fred') {
print "$name’comes after 'fred' in sorted order.\n";
}

Boolean 值

perl 沒有專門的 Boolean 值,真假值這樣判斷:

  • 如果值為數字,0 是 false;其余為真
  • 如果值為字符串,則空串(‘)為 false;其余為真
  • 如果值的類型既不是數字又不是字符串,則將其轉換為數字或字符串后再利用上述規則

這些規則中有一個特殊的地方。由於字符串'0' 和數字 0 有相同的標量值,Perl 將它們相同看待。也就是說字符串 '0' 是唯一一個非空但值為 0 的串。

用戶的輸入 <STDIN>

chomp 操作

$text = "a line of text\n"; # 也可以由<STDIN>輸入
chomp($text); #去掉換行符(\n)。

一步執行:

chomp ($text = <STDIN>); #讀入,但不含換行符

chomp 是一個函數。作為一個函數,它有一個返回值,為移除的字符的個數。這個數字基本上沒什么用:

$food = <STDIN>;
$betty = chomp $food; #得到值 1

如上,在使用 chomp 時,可以使用或不使用括號()。這又是 Perl 中的一條通用規則:除非移除它們時含義會變,否則括號是可以省略的。

while 控制結構

$count = 0;
while ($count < 10) {
$count + = 2;
print "count is now $count\n";
}

undef 值

變量被賦值之前使用它會有什么情況發生呢?通常不會有什么嚴重的后果。變量在第一次賦值前有一個特殊值 undef, 按照 Perl 來說就是:"這里什么也沒有,請繼續"。如果這里的“什么也沒有”是一些“數字”,則表現為 0。如果是“字符串”,則表現為空串。但 undef 既非數字也非字符串,它是另一種標量類型。

defined 函數

能返回 undef 的操作之一是行輸入操作,<STDIN>。通常,它會返回文本中的一行。但如果沒有更多的輸入,如到了文件的結尾,則返回 undef。要分辨其是 undef 還是空串,可以使用 defined 函數, ,如果其參數是 undef 值就返回 false,其他值返回 true。

$madonna = <STDIN>;
If ($defined ($madonna)){
print "The input was $madonna";
} else {
print "No input available!\n;
}

如果想聲明自己的 undef 值,可以使用 undef:

$madonna = undef ; #同$madonna 從未被初始化一樣。

列表和數組

#!/usr/bin/env perl -w
$fred[0] = "yabba";
$fred[1] = "dabba";
$fred[2] = "doo";
print @fred;
#print @fred."\n";

qw 簡寫

qw ! fred barney betty wilma dino !
qw# fred barney betty wilma dino # #有些像注釋
qw( fred barney betty wilma dino )
...

列表賦值

($fred, $barney, $dino) = ("flintstone", "rubble", undef);
($fred, $barney) = qw <flintstone rubble slate granite>; #兩個值被忽略了
($rocks[0],$rocks[1],$rocks[2],$rocks[3]) = qw/talc mica feldspar quartz/;

當想引用這個數組時, Perl 有一種簡單的寫法。在數組名前加@(后沒有中括號) 來引用整個數組。你可以把他讀作 "all of the "(所有的)”,所以@rocks 可以讀作 "all of the rocks(所有的石頭)"。其在賦值運算符左右均有效:

@rocks = qw / bedrock slate lava /;
@tiny = (); #空表
@giant = 1..1e5; #包含 100,000 個元素的表
@stuff = (@giant, undef, @giant); #包含 200,001 個元素的表
@dino = "granite";
@quarry = (@rocks, "crushed rock", @tiny, $dino);

pop 和 push 操作

@array = 5..9;
$fred = pop(@array); #$fred 得到 9,@array 現在為(5,6,7,8)
$barney = pop @array; #$barney gets 8, @array 現在為(5,6,7)
pop @array; #@array 現在為(5,6)(7 被丟棄了)
push(@array,0); #@array 現在為(5,6,0)
push @array,8; #@array 現在為(5,6,0,8)
push @array,1..10; #@array 現在多了 10 個元素
@others =qw/9 0 2 1 0 /;
push @array,@others; #@array 現在又多了 5 個元素(共有 19 個)

shift 和 unshift 操作

push 和 pop 對數組的末尾進行操作(或者說數組右邊有最大下標的元素,這依賴於你是怎樣思考的)。相應的, unshift 和 shift 對一個數組的開頭進行操作(數組的左端有最小下標的元素) 。下面是一些例子:

@array = qw# dino fred barney #;
$m = shift (@array); #$m 得到 "dino", @array 現在為 ("fred", "barney")
$n = shift @array; #$n 得到 "fred", @array 現在為 ("barney")
shift @array; #@array 現在為空
$o = shift @array; #$o 得到 undef, @arry 仍為空
unshift(@array,5); #@array 現在為(5)
unshift @array,4; #@array 現在為(4,5)
@others = 1..3;
unshift @array, @others; #array 現在為(1,2,3,4,5)

和 pop 類似,如果其數組變量為空,則返回 undef。

字符串中引用數組

和標量類似,數組也可以插入雙引號的字符串中。插入的數組元素會自動由空格分開:

@rocks = qw{ flintstone slate rubble };
print "quartz @rocks limestone\n"; #輸出為 5 種 rocks 由空格分開

foreach 控制結構

foreach $rock (qw/ bedrock slate lava /) {
print "One rock is $rock.\n" ; #打印出 3 種 rocks
}

這里的$rock不是這些列表元素中的一個拷貝而是這些元素本身

最常用的默認變量: $_

如果在 foreach 循環中省略了控制變量,那 Perl 會使用其默認的變量:$_。除了其不尋常的名字外,這和普通變量類似,如下面代碼所示:

foreach(1..10){ #使用默認的變量$_
print "I can count to $_!\n";
}
$_ = "Yabba dabba doo\n";
print; # 打印出默認變量 $_

reverse 操作

reverse(逆轉)操作將輸入的一串列表(可能是數組)按相反的順序返回。

@fred = 6 .. 10;
@barney = reverse (@fred); #得到 10,9,8,7,6
@wilma = reverse 6 . .10; #同上,沒有使用額外的數組
@fred = reverse @fred; #將逆轉過的字符串存回去

sort 操作

@rocks = qw/ bedrock slate rubble granite /;
@sorted = sort(@rocks); #得到 bedrock, granite, rubble, slate

標量和列表上下文

42 + something #something 必須是標量
sort something #something 必須是列表
@people = qw( fred barney betty );
@sorted = sort @people; #列表內容: barney , betty, fred
$number = 42 + @people; #標量內容:42+3,得到 45

另一個例子是 reverse。在列表 context 中,它返回反轉的列表。在標量 context 中,返回反轉的字符串(或者將反轉的結果串成一個字符串):

@backwards = reverse qw / yabba dabba doo /;
#返回 doo, dabba, yabba
$backwards = reverse qw/ yabba dabba doo /;
#返回 oodabbadabbay

在列表 Context 中使用 Scalar-Producing 表達式

如果一個表達式不是列表值,則標量值自動轉換為一個元素的列表:

@fred = 6*7;
@barney = "hello" . '' . "world";

強制轉換為標量 Context

偶爾,你可能需要標量 context 而 Perl 期望的是列表。這種情況下,可以使用函數 scalar。它不是一個真實的函數因為其僅是告訴 Perl 提供一個標量 context:

@rocks = qw(talc quartz jade obsidian);
print "How many rocks do you have?\n;
print "I have " @rocks, "rocks!\n"; # 錯誤,輸出 rocks 的名字
print "I have " scalar @rocks, "rocks!\n; # 正確,輸出其數字

<STDIN> 在列表 Context 中

@lines = <STDIN>; #將輸入讀入列表 context 中
chomp (@lines = <STDIN>); #讀入所有的行,不包括換行符

子程序

使用 sub 定義子程序

sub marine {
$n + = 1; #全局變量$n
print "Hello, sailor number $n!\n";
}

調用子程序

&marine; #輸出 Hello, sailor number 1!
&marine; #輸出 Hello, sailor number 2!
&marine; #輸出 Hello, sailor number 3!
&marine; #輸出 Hello, sailor number 4!

通常有括號,即便參數為空。子程序將繼承調用者的 @_ 的值。

參數

$n = &max(10,15); # 此子程序有 2 個參數

此參數列表被傳到子程序中;這些參數可以被子程序使用。當然,這些參存放在某個地方,在 Perl 中,會自動將此參數列表(此參數列表的另一個名字)自動存放在一個叫做@_的數組中。子程序可以訪問次數組變量來確定此參數的個數以及其值。這也就是說此子程序參數的第一個值存放在$_[0]中,第二個存放在$_1,依次類推。但必須強調的是這些變量和 $_ 這個變量沒有任何關系,如$dino3(數組 @dino 的一個元素)和$dino 的關系一樣。這些參數必須存放在某個數組變量中, Perl 存放在@_這個變量中。

 sub max{
if($_[0] > $_[1]) {
$_[0];
} else {
$_[1];
}
}

my 變量

foreach (1..10){
my($square) = $_*$_; #本循環中的私有變量
print "$_ squared is $squrare.\n";
}

變量$square 是私有的,僅在此塊中可見;在本例中,此塊為 foreach 循環塊。

當然,my 操作不會改變賦值參數的 context:

my ($num) = @_; # 列表 context, 同($sum) = @_;
my $num = @_; # 標量 context,同$num = @_;

使用 strict Pragma

use strict; #迫使采用更嚴格的檢測

省略 &

有些地方調用子程序可以不要&

my @cards = shuffle(@deck_of_cards); # &是不必要的

輸入和輸出

從標准輸入設備輸入

從標准輸入設備輸入是容易的。使用<STDIN>。在標量 context 中它將返回輸入的下一行:

$line = <STDIN>; #讀入下一行;
chomp($line); #去掉結尾的換行符
chomp($line=<STDIN>) #同上,更常用的方法

於,行輸入操作在到達文件的結尾時將返回 undef,這對於從循環退出時非常方便的:

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

從 <> 輸入

尖括號操作(<>)是一種特殊的行輸入操作。其輸入可由用戶選擇

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

調用參數

技術上講,<>從數組@ARGV 中得到調用參數。這個數組是 Perl 中的一個特殊數組,其包含調用參數的列表。換句話說,這和一般數組沒什么兩樣 (除了其名字有些特別: 全為大寫字母) ,程序開始運行時,調用參數已被存在@ARGV 之中了。

輸出到標准輸出設備

print @array; #打印出元素的列表
print "@array"; #打印一個字符串(包含一個內插的數組)

第一個語句打印出所有的元素,一個接着一個,其中沒有空格。第二個打印出一個元素,它為@array 的所有元素,其被存在一個字符串中。也就是說,打印出@array 的所有元素,並由空格分開。如果@array 包含 qw /fred barney betty /,則第一個例子輸出為:fredbarneybetty,而第二個例子輸出為 fred barney betty(由空格分開)。

使用 printf 格式化輸出

printf "Hello, %s : your password expires in %d days!\n",
$user, $days_to_die;
printf "%6f\n" 42; # 輸出為 ○○○○ 42 (○此處指代空格)
printf "%23\n",2e3+1.95; # 2001

數組和 printf

my @items = qw( wilma dino pebbles );
my $format = "The items are:\n". ("%10s\n" x @items);
printf $format, @items;

等同:

printf "The items are:\n". ("%10s\n"x @items), @items;

本處 @items 有兩個不同的 context (上下文),第一次表示元素個數,第二次表示列表中的所有元素。

句柄(即文件描述符)

Perl 自身有六個文件句柄: STDIN,STDOUT,STDERR,DATA,ARGV,ARGVOUT

文件句柄的打開

open CONFIG, "dino" ;
open CONFIG, "<dino" ;
open BEDROCK, ">fred" ;
open LOG, ">>logfile" ;

Perl 的新版本中(從 Perl5.6 開始),open 支持“3 參數”類型:

open CONFIG, "<", "dino";
open BEDROCK, ">", $file_name;
open LOG, ">>", &logfile_name();

Bad 文件句柄

關閉文件句柄

close BEDROCK;

嚴重錯誤和 die

可以和 C 中使用 perror 類似,用 die 函數:

if (!open LOG, ">>logfile") {
die "Cannot create logfile:$!";
}

使用文件句柄

if (!open PASSWD, "/etc/passwd") {
die "How did you get logged in?($!)";
}
while (<PASSWD>) {
chomp;
...
}

哈希

什么是哈希

和 Python 的字典一樣

哈希元素的存取

$hash {$some_key}

作為整體的 hash

要引用整個 hash,使用百分號(“%” )作為前綴。

%some_hash = ("foo",35, "bar", 12.4, 2.5, "hello", "wilma", 1.72e30, "betty", "bye\n");

hash 的值(在列表 context 中)是一個 key/value 對的列表:

@array_array = %some_hash;

哈希賦值

%new_hash = %old_hash;
%inverse_hash = reverse %any_hash;

大箭頭符號 (=>)

my %last_name = (
"fred” => "flintstone",
"dino" => undef,
"barney" => "rubble",
"betty" => "rubble",
);

哈希函數

keys 和 values

my %hash = ("a" => 1, "b" => 2, "c" => 3);
my @k = keys %hash;
my @v = values %hash;

each 函數

while (($key, $value) = each %hash) {
print "$key => $value\n";
}

exists 函數

if (exists $books{$dino}) {
print "Hey, there's a libaray card for dino!\n";
}

delete 函數

my $person = "betty";
delete $books{$person}; # 將$person 的借書卡刪除掉

正則表達式

簡單的模式

$_ = "yabba dabba doo";
if (/abba/) {
print "It matched!\n";
}

所有在雙引號中的轉義字符在模式中均有效,因此你可以使用 /coke\tsprite/ 來匹配 11 個字符的字符串 coke, tab(制表符),sprite。

元字符

.
?
+
*

模式中的分組

/fred+/ # 只能匹配 fredddddd 等
/(fred)+/ # 能匹配 fredfredfred 等
/(fred)*/ # 可以匹配 "hello,world",因為 * 是匹配前面的 0或多次

選擇符 (|)

/fred|barney|betty/
/fred( |\t)+barney/

字符類

指[]中的一列字符。

字符類的簡寫
\d == [0-9]
\w == [A-Za-z0-9_]
\s == [\f\t\n\r] # 格式符(form-feed)、制表符(tab)、換行符、回車
簡寫形式的補集
\D == [^\d]
\W == [^\w]
\S == [^\s]

可以組合:

[\d\D] # 任何數字和任何非數字,可以匹配所有字符!比如 . 是不能匹配所有字符的
[^\d\D] # 無用

正則表達式的應用

使用 m// 匹配

同 qw // 一樣,可以使用任何成對字符,比如可以使用 m(fred), m<fred>, m{fred}, m[fred],或者 m,fred,, m!fred!, m^fred^。

如果使用 / 作為分隔符,可以省略前面的 m

如果使用配對的分隔符, 那不用當心模式內部會出現這些分隔符, 因為通常模式內部的分隔符也是配對的。因此, m(fred(.*)barney), m{\w{2,}}, m[wilma[\n \t]+betty]是正確的。對於尖括號(<和>),它們通常不是配對的。如模式 m{(\d+)\s*>=?\s*(\d+)},如果使用尖括號,模式中的尖括號前因當使用反斜線(\), 以免模式被過早的結束掉。

可選的修飾符

不區分大小寫: /i

if (/yes/i) { #大小寫無關
print "In that case, I recommend that you go bowling.\n";
}

匹配任何字符: /s

使用/s 這個修飾符,它將模式中點 (.) 的行為變成同字符類 [\d\D] 的行為類似:可以匹配任何字符,包括換行符。從下例中可見其區別:

$_ = "I saw Barney\ndown at the bowing alley\nwith Fred\nlast night.\n;
if (/Barney.*Fred/s) {
print "That string mentions Fred after Barney!\n";
}

添加空格: /x

/x 修飾符,允許你在模式中加入任何數量的空白,以方便閱讀:

/-?\d+\.?\d*/ # 這是什么含義?
/ -? \d+ \.? \d* /x # 要好些

Perl 中,注釋可以被作為空白,因此使用/x,可以在模式中加上注釋:

/
-? #可選的負號
d+ #小數點前一個或多個十進制數字
\.? #可選的小數點
\d* #小數點后一些可選的十進制數字
/x #模式結束

使用多個修飾符

if (/barney.*fred/is/){ # /i 和/s
print "That string mentions Fred after Barney!\n";
}

錨定

^ 開頭
$ 結尾

注意:/^fred$/能同時匹配上 "fred" 和 "fred\n"。

/^\s*$/ # 匹配空行

詞錨定: \b

/\bfred\b/ 可以匹配單詞 "fred", 但不能匹配 "frederick"

綁定操作符: =~

對 $_ 進行匹配只是默認的行為, 使用綁定操作符 ( =~ ) 將告訴 Perl 將右邊的模式在左邊的字符串上進行匹配,而非對 $_ 匹配。

my $some_other = "I dream of betty rubble.";
if ($some_other =~ /\brub/) {
print "Aye, there's the rub.\n";
}

綁定操作符優先級較高:

my $likes_perl = <STDIN> =~ /\byes\b/i;

匹配變量

$_ = "Hello there, neighbor";
if (/\s(\w+),/) { #空格和逗號之間的詞
print "The word was $1\n";
}

自動匹配變量

$& # 整個被匹配的部分
$` # 匹配部分的前一部分存放在$`之中
$' # 后一部分被存到$'

使用正則表達式處理文件

使用 s/// 進行替換

$_ = "He's out bowling with Barney tonight.";
s/Barney/Fred/; # Barney 被 Fred 替換掉
print "$_\n";
#接上例:現在 $_ 為 "He's out bowling with Fred tonight."
s/Wilma/Betty/; # 用 Wilma 替換 Betty(失敗)
s/with (\w+)/agaist $1's team/;
print "$_\n"; # 為 "He's out bowling against Fred's team tonight.";

使用 /g 進行全局替換

$_ = "home, sweet home!";
s/home/cave/g;
print "$_\n"; # "cave, sweet cave!"

全局替換的一個常用地方是將多個空格用單個空格替換掉:

$_ = "Input data\t may have extra whitespace.";
s/\s+/ /g; # 現在是 "Input data may have extra whitespace."

現在已經知道怎樣去掉多余的空格,那怎樣去掉開頭和結尾的空白呢?這是非常容易的:

s/^\s+//; #將開頭的空白去掉
s/\s+$//; #將結尾的空白去掉
s/^\s+|\s+$//g; #將開頭,結尾的空白去掉

不同的分隔符

如同 m//和 qw//一樣,我們也可以改變 s///的分隔符。但這里使用了3個分隔符,因此有些不同。

s#^https://#http://#;

如果使用的是配對的字符,也就是說其左字符和右字符不的,則必需使用兩對:一對存放模式,一對存放替換的字符串。此時,分隔符甚至可以是不同的。事實上,分隔符還可以使用普通的符號(非配對的)。下面三例是等價的:

s{fred}{barney};
s[fred](barney);
s<fred>#barney#;

可選的修飾符

除了/g 修飾符外,替換操作中還可以使用 /i , /x ,和 /s,這些在普通的模式匹配中已經出現過的修飾符。其順序是無關緊要的。

s#wilma#Wilma#gi; # 將所有的 WilmA,或者 WILMA 等等,由 Wilma 替換掉
s{_ _END_ _.*}{ }s; # 將 END 標記及其后面的行去掉

綁定操作

同 m// 一樣,我們也可以通過使用綁定操作符改變 s/// 的替換目標:

$file_name =~ s#^.*###s; # 將$file_name 中所有的 Unix 類型的路徑去掉

大小寫替換

有時,希望確保被替換的字符串均是大寫的(或者不是,視情況而定) 。這在 Perl 中只需使用某些修飾符就能辦到。 \U 要求緊接着的均是大寫:

$_ = "I saw Barney with Fred.";
s/(fred|barney)/\U$1/gi; # $_ 現在是 "I saw BARNEY with FRED."

同樣,也可以要求后面的均為小寫 \L :

s/(fred)|barney/\L$1/gi; #$_現在是 "I saw barney with fred."

默認時,會影響到剩余的(替換的)字符串。可以使用 \E 來改變這種影響:

s/(\w+) with (\w+)/\U$2\E with $1/I; # $1 現在是 "I saw FRED with barney."

使用小寫形式時( \l 和 \u ),只作用於下一個字符:

s/ (fred|barney)/\u$1/ig; #$_現在是 "I saw FRED with Barney."

也可以同時使用它們。如使用\u 和\L 表示 "第一個字母大寫,其它字母均小寫"。 \L 和\u 可以按任意順序出現。Larry 意識到人們有時可能按相反順序使用它們,因此他將 Perl 設計成,在這兩種情況下都是將第一個字母大寫,其余的小寫。 Larry 是個非常好的人。

s/(fred|barney)/\u\L$1/ig; #$_現在為 "I saw Fred with Barney."

這些在替換中出現的大小寫轉換的修飾符,也可在雙引號中使用:

print "Hello, \L\u$name\E, would you like to play a game?\n";

split 操作

另一個使用正則表達式的操作是 split , 它根據某個模式將字符串分割開。這對於由制表符分割開,冒號分割開,空白分割開,或者任意字符分割開的數據是非常有用的。任何可在正則表達式之中 (通常,是一個簡單的正則表達式) 指定分離符 (separator) 的地方,均可用 split。其形式如下:

@fields = split /separtor/, $string;
@fields = split /:/, "abc:def:g:h"; # 返回 ("abc", "def", "g", "h")

/\s+/ 這個模式分隔符非常常見:

my $some_input = "This is a \t test.\n";
my @args = split /\s+/, $some_input; # ("This", "is", "a", "test." )

默認時,split 對$_操作,模式為空白:

my @fields = split; # 同 split /\s+/, $_;

join 函數

join 函數不使用模式,但它完成同 split 相反的操作:split 將一個字符串分割開,而 join 函數將這些分割的部分組合成一個整體。join 函數類似於:

my $result = join $glue, @pieces;

join 函數的第一個參數是粘合元素(glue),它可以是任意字符串。剩下的參數是要被粘合的部分。join 將粘合元素添加在這些部分之間,並返回其結果:

my $x = join ":", 4, 6, 8, 10, 12; #$x 為 "4:6:8:10:12"

列表上下文中的 m//

在列表 context 中使用模式匹配(m//)時,如果匹配成功返回值為內存變量值的列表;如果匹配失敗則為空列表:

$_ = "Hello there, neighbor!";
my($first, $second, $third) =/(\S+) (\S+), (\S+)/;
print “$second is my $third\n” ;

在 s/// 中介紹的 /g 修飾符也可在 m// 中使用,它允許你在字符串中的多處進行匹配。在這里,由括號括起來的模式將在每一次匹配成功時返回其內存中所存放的值:

my $text = "Fred dropped a 5 ton granite block on Mr. Slate";
my @words = ($text =~ /([a-z]+)/ig);
print "Result: @words\n";
#Result: Fred dropped a ton granite block on Mr slate

如果有不止一對括號,每一次返回不止一個字符串。例如將字符串放入 hash 中,如下:

my $data = "Barney Rubble Fred Flintstone Wilma Flintstone";
my %last_name = ($data =~ / (\w+)\S+(\w+)/g);

每當模式匹配成功時,將返回一對值。這些一對一對的值就成了 hash 中的 key/value 對。

更強大的正則表達式

非貪婪的數量詞

各符號對應的非貪婪匹配:

+ --> /fred.+?barney/
* --> s#<BOLD>(.*?)</BOLD>#$1#g;
{5,10}?
{8,}?
? --> ??

在命令行進行修改

$ perl –p –i.bak –w –e 's/Randall/Randal/g' fred*.dat

-p 要求 Perl 為你寫一個程序。它算不上是一個完整的程序;看起來有些像下面的:

while(<>) {
print;
}

更多控制結構

unless 控制結構

在 if 控制結構中,只有條件為真時,才執行塊中的代碼。如果你想在條件為假時執行,可以使用 unless:

unless ($fred =~ /^[A-Z_]\w*$/i) {
print "The value of \$fred doesn't look like a Perl identifier name.\n";
}

unless 的含義是:除非條件為真,否則執行塊中的代碼。這和在 if 語句的條件表達式前面加上!(取反)所得結果是一樣的。

另一種觀點是,可以認為它自身含有 else 語句。如果不太明白 unless 語句,你可以把它用 if 語句來重寫(頭腦中,或者實際的重寫) :

if ($fred =~ /^[A-Z_]\w*$/i) {
#什么也不做
} else {
print "The value of \$fred doesn't look like a Perl identifier name.\n";
}

unless 和 else 一起

unless ($mon =~ /^Feb/) {
print "This month has at least thirty days.\n";
} else {
print "Do you see what's going on here?\n";
}
也可以同時使用它們

until 控制結構

有時,希望將 while 循環的條件部分取反。此時,可以使用 until:

until ($j > $i) {
$j *= 2;
}

表達式修飾符

為了得到更緊湊的形式,表達式后可以緊接控制修飾語。如,if 修飾語可以像 if 塊那樣使用:

print "$n is a negative number.\n" if $n<0;

等同下面代碼:

if ($n < 0) {
print "$n is a negative number.\n";
}

雖然被放在后面,條件表達式也是先被求值的。這和通常的的從左到右的順序相反。要理解 Perl 代碼,應當像 Perl 內部的編譯器那樣,將整個語句讀完,來其具體的含義。

還有一些其它的修飾語:

&error ("Invalid input") unless &valid($input);
$i *= 2 unitl $i > $j;
print "", ($n += 2) while $n <10;
&greet($_) foreach @person;

The Naked Block 控制結構

被稱為“裸的”塊是指沒有關鍵字或條件的塊。假如有一個 while 循環,大致如下:

while(condition) {
body;
body;
body;
}

將關鍵字 while 和條件表達式去掉,則有了一個“裸的”塊:

{
body;
body;
body;
}

“裸的”塊看起來像 while 或 foreach 循環,除了它不循環外;它執行“循環體”一次,然后結束。它根本就沒循環!

你也可能在其它地方使用過“裸的”塊,這通常是為臨時變量提供作用域:

{
print "Please enter a number:";
chomp(my $n = <STDIN>);
my $root = sqrt $n; #計算平方根
print "The square root of $n is $root.\n";
}

elsif 語句

自增和自減 (同C)

my @people = qw{ fred barney fred Wilma dino barney fred pebbles};
my %count; #新的空的 hash
$count{$_}++ foreach @people; #根據情況創建新的 keys 和 values

第一次執行 foreach 循環時,$count{$_}自增1。此時,$count{"fred"} 從 undef (由於之前 hash 中不存在)變成1。第二次執行 foreach 循環時,$count{"barney"} 變成1; 接着,$count{"fred"} 變成2。每執行一次循環,%count 中的某個元素增1,或者新元素被創建。循環結束時,$count{"fred"} 值為3。這提供了一種查看某個元素是否存在於列表之中,以及其出現次數的方法。

for 控制結構 (同C)

for (initialization; test; increment) {
body;
body;
}

foreach 和 for 的關系

對於 Perl 解析器(parser)而言, 關鍵字 foreach 和 for 是等價的。也就是說, Perl 遇見其中之一時, 和遇見另一個是一樣的。Perl 通過括號能理解你的目的。如果其中有兩個分號,則是 for 循環(和我們剛講的類似); 如果沒有,則為 foreach 循環:

for (1..10) { #實際上是 foreach 循環,從1到10
print "I can count to $_!\n";
}

這實際是一個 foreach 循環,但寫作 for。除了本例外,本書后面均寫作 foreach。在實際代碼中,你覺得 Perler 會輸入這四個多余的字符嗎?除了新手外,一般均寫作 for,你也應當像 Perl 那樣,通過查看括號內分號個數來判斷。

循環控制

last 操作

last 會立刻結束循環。(這同 C 語言或其它語言中的 "break" 語句類似)。它從循環塊中“緊急退出” 。當執行到 last,循環即結束,如下例:

#輸出所有出現 fred 的行,直到遇見 _ _END_ _標記
while(<STDIN>){
if(/_ _ END_ _/){
#這個標記之后不會有其它輸入了
last;
}elsif(/fred/){
print;
}
}
##last 跳轉到這里##

next 操作 (同C的中 continue)

redo 操作

循環控制的第三個操作是 redo。它會調到當前循環塊的頂端,不進行條件表達式判斷以及接着本次循環。(在 C 或類似語言中沒有這種操作)下面是一個例子:

# 輸入測試
my @words = qw{ fred barney pebbles dino Wilma betty };
my $errors = 0;
foreach(@words){
##redo 跳到這里##
print "Type the word '$_' : ";
chomp(my $try = <STDIN>);
if($try ne $_){
print "sorry -- That's not right.\n\n";
$errors++;
redo; #跳轉到循環頂端
}
}
print "You've completed the test, with $errors errors.\n";

標簽塊

要給循環體加上標簽,可在循環前面加上標簽和冒號。在循環體內,可以根據需要在 last, next,或n者 redo 后加上標簽名,如:

LINE: while(<>){
foreach (split){
last LINE if /_ _END_ _/; # 退出 LINE 循環
...
}
}

邏輯操作符 && 和 ||

短路操作符

和 C(以及類似的語言)不同的地方是,短路操作的結果是最后被執行語句的返回值,而非僅僅是一個 Boolean 值。

my $last_name = $last_name{$someone} || '(No last name)';

三元操作符 ?:

當 Larry 決定 Perl 應當具有哪些操作符時,他不希望以前的 C 程序員在 Perl 中找不到 C 中出現過的操作符,因此他在 Perl 中實現了所有 C 的操作符。這意味着 C 中最容易混淆的操作符:三元操作符(?:)也被移植過來了。雖然它帶來了麻煩,但有時也是非常有用的。

Express ? if_true_expr : if_false_expr
my $size =
($width < 10 ) ? "small":
($width < 20) ? "medium":
($width < 50) ? "large":
"extra_large"; #default

控制結構:使用部分求值的操作符

前面三個操作符,均有一個共同的特殊性質:根據左側的值(true 或 false),來判斷是否執行右側代碼。有時會被執行,有時不會。由於這個理由,這些操作符有時叫做部分求值(partial-evaluation)操作符,因為有時並非所有的表達式均被執行。部分求值操作符是自動的控制結構。並非 Larry 想引入更多的控制結構到 Perl 中來。而是當他決定將這些部分求值操作符引進 Perl 后,它們自動成了控制結構。畢竟,任何可以激活或解除某塊代碼的執行即被稱作控制結構。

文件校驗

文件檢測操作

如果程序會建立新的文件,在程序創建新文件之前,我們應先確定是否存在同名的文件,以免重要數據被覆蓋掉。對於這種問題,我們可以使用– 選項,檢測是否存在相同名字的文件:

die "Oops! A file called '$filename' already exists.\n"
if -e $filename;

如果文件在過去28 天內都未被修改,輸出警告:

warn "Config file is looking pretty old!\n"
if -M CONFIG > 28;

下例首先檢查文件列表,找到那些大於 100KB 的文件。如果一個文件僅是很大, 我們不一定會將其移到備份磁帶上去,除非同時其在最近 90 天內都未被訪問。

my @ariginal_files = qw/ fred barney betty Wilma pebbles dino bam-bamm /;
my @big_old_files; #要移到備份磁帶上的文件
foreach my $filename (@original_files){
push @big_old_files, $filename
if -s $filename > 100_100 and -A $filename > 90;
}
-r 文件或目錄對此(有效的)用戶(effective user)或組是可讀的
-w 文件或目錄對此(有效的)用戶或組是可寫的
-x 文件或目錄對此(有效的)用戶或組是可執行的
-o 文件或目錄由本(有效的)用戶所有
-R 文件或目錄對此用戶(real user)或組是可讀的
-W 文件或目錄對此用戶或組是可寫的
-X 文件或目錄對此用戶或組是可執行的
-O 文件或目錄由本用戶所有
-e 文件或目錄名存在
-z 文件存在,大小為 0(目錄恆為 false)
-s 文件或目錄存在,大小大於 0(值為文件的大小,單位:字節)
-f 為普通文本
-d 為目錄
-l 為符號鏈接
-S 為 socket
-p 為管道(Entry is a named pipe(a "fifo"))
-b 為 block-special 文件(如掛載磁盤)
-c 為 character-special 文件(如 I/O 設備)
-u setuid 的文件或目錄
-g setgid 的文件或目錄
-k File or directory has the sticky bit set
-t 文件句柄為 TTY(系統函數 isatty()的返回結果;不能對文件名使用這個測試)
-T 文件有些像“文本”文件
-B 文件有些像“二進制”文件
-M 修改的時間(單位:天)
-A 訪問的時間(單位:天)
-C 索引節點修改時間(單位:天)

stat 和 lstat 函數

my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blockes)
= stat($filename);

localtime 函數

my($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst)
= localtime $timestamp;
my $timestamp = 1180630098;
my $date = localtime $timestamp;

gmtime

my $now = gmtime; #得到當前的時間

位操作

10 & 12 按位與;當操作數相應位均為 1 時結果為 1 (本例結果為 8)
10 豎線 12 按位或;當操作數中相應位有一個為 1 則結果為 1(本例結果為 14)
10 ^ 12 按位異或;當操作數中相應位有且僅有一個為 1,結果才為 1(本例結果 6)
6 << 2 位左移,將左邊操作數左移右邊操作數所指定的位數,被移出的位置(右邊)補 0(結果為 24)
25 >> 2 位右移,將左邊操作數右移動右邊操作數所指定的位數,丟棄多余的位數(左邊) (本例結果位 6)
~10 位取反,也叫做一元位補運算;其返回值為操作數中相應位取反的值(本例為 0Xfffffff5,這和具體情況有關)

目錄操作

在目錄樹上移動

chdir "/etc" or die "cannot chdir to /etc: $!";

由於這是系統請求,錯誤發生時將給變量$!賦值。通常應當檢查$!的值,因為它將告訴你 chdir 失敗的原因。

Globbing

通常,shell 將每個命令行中的任何的文件名模式轉換成它所匹配的文件名。這被稱作 globbing。例如,在 echo 命令后使用了文件名模式*.pm,shell 會將它轉換成它所匹配的文件名:

$ echo *.pm
barney.pm dino.pm fred.pm wilma.pm
$
$ cat > show_args
foreach $arg (@ARGV) {
print "one arg is $arg\n";
}
^D
$ perl show_args *
...

show-args 不需要知道如何進行 globbing,這些名字已經被處理后存在@ARGV 中了。

在 Perl 程序中也可以使用 Glob 匹配。

my @all_files = glob "*" ;
my @pm_files = glob "*.pm";

@all_files 得到了當前目錄下的所有文件,這些文件按照字母排序的,不包括由點 (.)開頭的文件。

Globbing 的替換語法

雖然我們任意的使用 globbing 這個術語,我們也談論 glob 操作,但在許多使用 globbing 的程序中並沒有出現 glob 這個字眼。為什么呢?原因是,許多這類代碼在 glob 操作被命名前就寫好了。它使用一對尖括號(<>), 和從文件句柄讀入操作類似:

my @all_files = <*>; # 基本上同 @all_files = glob "*" 一樣
my $dir = "/etc";
my @dir_files = <$dir/* $dir/.*>;

目錄句柄

從給定目錄得到其文件名列表的方法還可以使用目錄句柄(directory handle)。目錄句柄外形及其行為都很像文件句柄。打開(使用 opendir 而非 open),從中讀入(使用 readdir 而非 readline),關閉(使用 closedir 而非 close)。不是讀入文件的內容,而是將一個目錄中的文件名(以及一些其它東西)讀入,如下例:

my $dir_to_process = "/etc";
opendir DH, $dir_to_process or die "Cannot open $dir_to_process: $!";
foreach $file(readdir DH) {
print "One file in $dir_to_process is $file\n";
}
closedir DH;

刪除文件 unlink

unlink "slate", "bedrock", "lava";

這里有一個鮮為人知的 Unix 事實。你可以有一個文件不可讀,不可寫,不可執行,甚至你並不擁有它,例如屬於別人的,但你仍然可以刪除它。這時因為 unlink 一個文件的權限同文件本身的權限是沒有關系的;它和目錄所包含的文件權限有關。

重命名文件

rename "over_there/some/place/some_file", "some_file";

鏈接文件

要找出符號連接指向的地方,使用 readlink 函數。它會告訴你符號連接指向的地方,如果參數不是符號連接其返回 undef:

my $where = readlink "carroll"; # 得到 "dodgson"
my $perl = readlink "/usr/local/bin/perl" # 可能得到 Perl 放置的地方

創建和刪除目錄

mkdir "fred", 0755 or warn "Cannot make fred directory: $!";
rmdir glob "fred/*"; # 刪除 fred/下面所有的空目錄

修改權限

chmod 0755, "fred", "barney";

改變所有者

my $user = 1004;
my $group = 100;
chown $user, $group, glob "*.o";

如果要使用用戶名:

defined(my $user = getpwnam "merlyn") or die "user bad";
defined(my $group = getprnam "users") or die "group bad";
chown $user, $group, glob "/home/Merlyn/*";

defined 函數驗證返回值是否為 undef, 如果請求的 user 或 group 不存在,則返回 undef。chown 函數返回其改變的文件的個數,而錯誤值被設置在$!之中。

改變時間戳

my $now = time;
my $ago = $now -24*60*60; # 一天的秒數
utime $now,$ago,glob "*" # 設成當前訪問的,一天之前修改的

字符串和排序

使用索引尋找子串

$where = index($big, $small);

Perl 查找子串第一次在大字符串中出現的地方,返回第一個字符的位置。字符位置是從 0 開始編號的。如果子串在字符串的開頭處找到,則 index 返回 0。如果一個字符后,則返回1,依次類推。如果子串不存在,則返回-1。

使用 substr 操作子串

$part = substr($string, $initial_position, $length);

它有三個參數:一個字符串,一個從 0 開始編號的初始位置(類似於 index 的返回值) ,以及子串的長度。返回值是一個子串:

my $mineral = substr("Fred J. Flintstone", 8, 5); # 得到 "Flint"
my $rock = substr "Fred J. Flintstone", 13, 1000; # 得到 "stone"
my $pebble = substr "Fred J. Flintsone", 13; # 同上,得到 "stone"
my $out = substr ("some very long string", -3, 2); # $out 得到 "in"

substr 選則的子串可以就地修改!

#!/usr/bin/perl -w
use strict;
my $string = "Hello, world!";
substr($string,6) = "Jian Lee!";
print $string . "\n";

最后輸出為: “Hello,Jian Lee!”

使用 sprintf 格式化數據

sprintf 函數的參數和 printf 的參數完全相同(除了可選的文件句柄外), 但它返回的是被請求的字符串,而非打印出來。這對於希望將某個格式的字符串存入變量以供將來使用的情況非常方便,或者你想比 printf 提供的方法,更能控制結果:

my $data_tag = sprintf "%4d/%02d/%02d %02d:%02d:%02d", $yr, $mo, $da, $h, $m, $s;

進程管理

system 函數

在 Perl 中調用子進程來運行程序的最簡單方法是使用 system 函數。例如,要調用 Unix 的 date 命令,看起來如下:

system "date";

系統函數的參數和通常在 shell 中輸入的是一樣的。如果是復雜一些的命令,如 “ – $HOME” ,我們也只需將它們放入參數中:

system 'ls -l $HOME';

我們這里將雙引號變成了單引號,因為$HOME 是一個 shell 變量。否則,shell 看不到美元符號,因為 Perl 會將它用值進行替換。當然,我們也可以這樣寫:

system "ls -l \$HOME";

exec 函數

exec 和 system 完全一樣,除了一點!exec 打開一個進程代替當前的 Perl 程序教程,exec 執行的進程結束不再返回到perl教程。

環境變量

Perl ,環境變量可以由 %ENV 得到。

$ENV{'PATH'} = "/home/rootbeer/bin:$ENV('PATH')";
delete $ENV{'IFS'};
my $make_result = system "make";

使用反引號得到輸出

my $now = `date`; # 捕獲 date 的輸出
print "The time is now $now"; # 已經有換行符

像文件句柄一樣處理進程

開始一個並發(並行)子進程的語法是將命令作為“文件名”傳給 open,並在其前面或后面加上豎線(|),豎線是“管道(pipe)”符。基於這些原因,這通常叫做管道打開 (piped open) :

open DATE, "date|" or die "Cannot pipe from date: $!";
open MAIL, "|mail merlyn" or die "Cannot pipe to mail: $!";

fork 函數

defined (my $pid= fork) or die "Cannot fork: $!";
unless ($pid){
#子進程在這里
exec "date";
die "cannot exec date: $!";
}
#父進程在這里
waitpid($pid, 0);

發送和接受信號

kill 2, 4201 or die "Cannot signal 4201 with SIGINT: $!";

Perl 模塊

查找

http://search.cpan.org/

$ perldoc CGI

安裝模塊

通常模塊使用 MakeMaker ,可以這樣安裝:

$ perl Makerfile.PL
# $ perl Makerfile.PL PREFIX=/Users/fred/lib
$ make install

一些模塊的創建者使用另一個模塊:Module::Build,來構造及安裝他們的模塊。此時的命令如下:

$ perl Build.PL
$./Build install

使用 CPAN.pm

$ perl -MCPAN -e shell
$ cpan Module::CoreList LWP CGI::Prototype

使用簡單的模塊

File::Basename

use File::Basename;
my $name = "/usr/local/bin/perl";
my $basename = basename $name; #得到 "perl"

僅使用模塊中的一些函數

use File::Basename qw/ basename /;

下面做法不引入任何函數:

use File::Baename qw/ /;
use File::Basename ( );

沒有引入任何函數時,如果要使用函數,就必須用全名:

use File:Basename qw/ /; # 沒有引入函數
my $betty = &dirname($wilma); # 使用我們自己的子程序 &dirname(這里沒有顯示)
my $name = "/usr/local/bin/perl";
my $dirname = File::Basename::dirname $name; # 使用模塊中的 dirname

File:Spec 模塊

use File::Spec;
#得到上面的$dirname, $basename 的值
my $new_name = File::Spec->catfile($dirname, $basename);
rename($old_name, $new_name)
or warn "Can't rename '$old_name' to '$new_name': $!";

一些高級的 Perl 技術

利用 eval 捕捉錯誤

eval { $barney = $fred / $dino }; # 即使除數為0也不會終止


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM