Perl笔记3

22 哈希
一种数据结构,它和数组的不同在于索引方式,数组是以数字来索引,哈希则以名字来索引。
哈希由键值对组成,键必须是唯一的字符串,而值可以是数字,字符串,undef或者这些类型的组合
(1) 访问哈希元素
语法:
$hash{$some_key}
注意其与数组的区别,数组使用[],而且数组的键是数字。
$family_name{'fred'} = 'flintstone';
$family_name{'barney'} = 'rubble';
foreach my $person (qw/fred barney/)
{
    print "I've heard of $person $family_name{$person}.\n";
}
Result:
I've heard of fred flintstone.
I've heard of barney rubble.

(2) 访问整个哈希
要指代整个哈希,可以用百分号(%)作为前缀,
如前面的哈希为%family_name

哈希可以被转换成列表
%some_hash = ('foo', 35, 'bar', 12.4, 2.5, 'hello', 'wilma', 1.45e30, 'betty', "bye");
在列表上下文中,哈希的值是简单的键值对列表:
@any_array = %some_hash;
print "@any_array\n";
结果如下(顺序不一定是之前插入的顺序):
betty bye bar 12.4 wilma 1.45e+30 foo 35 2.5 hello

(3) 哈希赋值
这不是常见的用法
my %new_hash = %old_hash;
解释:过程不是简单的复制内存块,而是大概先将%old_hash展开为列表,然后将列表重新组建成新键-值对,形成新的哈希。

%ip_address = reverse %host_name;
#将哈希%host_name中的键值对对换,形成新的哈希%ip_address。但是这种用法最好确保原来哈希的值没有重复的,否则哈希遵循一个
约定"后发先至",即用列表中最后的键覆盖之前的键。

(4) 胖箭头
只是逗号的另一种写法,方便区分键值
my %last_name = (
    fred => 'dog', #胖箭头左边的裸字会自动加上引号。
    dino => 'cat',
    barney => 'apple',
    betty => 'apple',
    );
   
但是如果键为特殊符号时,比如加号+,就不能省略引号了。   
   
另外在花括号中检索特定键名的元素时,也可以省略键名的引号。   
$score{'fred'}可以写成$score{fred}
但是也要注意特殊情况:
$hash{bar.foo} = 1; #bar.foo会先进行字符串计算,得barfoo作为键

(5) 哈希函数
函数1:keys和values函数
keys函数返回哈希的键列表
values函数返回对应的值列表

实例:
my %hash = ('a'=>1, 'b'=>2, 'c'=>3);
my @k = keys %hash;
my @v = values %hash;
print "The %hash's keys is : @k\n";
print "The %hash's values is : @v\n";

测试结果:
The %hash's keys are : c a b
The %hash's values are : 3 1 2

在标量上下文中,这两个函数返回哈希中元素(键-值对)的个数:
my $count = keys %hash; #得到3

也可以将哈希当成布尔值表达式判断真假
if (%hash)
{
    print "That was a true value!\n";
}
只要hash中有一个键值对就为true.

函数2:each函数
它以包含两个元素的列表形式返回键值对。
实际使用时,唯一适合使用each的地方就是在while循环中:
my %hash = ('a'=>1, 'b'=>2, 'c'=>3);
while (($key, $value) = each %hash) #列表($key, $value)在标量上下文中统计个数,只要个数不会0,while循环就执行
{
    print "$key => $value\n";
}
Result:
c => 3
a => 1
b => 2

实例:
排序输出哈希中的键值对
my %hash = ('a'=>1, 'b'=>2, 'c'=>3);
foreach my $key (sort keys %hash)
{
    print "$key => $hash{$key}\n";
}
Result:
a => 1
b => 2
c => 3


(6) 哈希函数应用
exists 函数
判断哈希中是否存在某个键,返回真假,与键对应的值无关
if (exists $books{"dino"})
{
    print "Hey, there's a library card for dino!\n";
}

delete 函数
my $person = "betty";
delete $books{$person};

哈希元素内插:
$books{'fred'} = 3;
$books{'wilma'} = 1;
$books{'hello'} = 0;
$books{'tom'} = undef;
foreach my $person (sort keys %books)
{
    if ($books{$person})
    {
        print "$person has $books{$person} items\n";
    }
}
Result:
fred has 3 items
wilma has 1 items

(7) %ENV哈希
从%ENV中访问操作系统环境的PATH的值
print "$ENV{'PATH'}\n";
Result:
/opt/vertica/bin:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/oracle/product/10.2.0/db_1/bin:...
在Linux上面参考PATH环境变量的值一致:
[root@etl10 hash]# echo $PATH
/opt/vertica/bin:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/oracle/product/10.2.0/db_1/bin:...
/

测试:
my (@words, %count, $word);
chomp(@words = <STDIN>);
foreach $word (@words)
{
    $count{$word} += 1;
}

foreach $word (keys %count)
{
    print "$word was seen $count{$word} times.\n";
}

测试结果:
[root@etl10 hash]# perl 3.pl
hello
dog
cat
dog
cat
dog
ket
cat was seen 2 times.
ket was seen 1 times.
dog was seen 3 times.
hello was seen 1 times.


23 正在表达式
(30)字符集的简写
从Perl 5.14引入了一种新的修饰符\a,当你需要严格按照ASCII的范围来匹配数字字符时,可以用这种方式。
use 5.014;
$_ = 'The HAL-900 requires authorization to continue.';
if (/HAL-[\d]+/a) #\a写在最后面
{
    say 'The string mentions some model or HAL computer.';
}

 

 

24 用正则表达式进行匹配
(1) 锚位
通过给定锚位,让模式仅在字符串指定位置匹配。
\A行首锚位 匹配字符串的绝对开头
m{\Ahttps?://}i 判断字符串是否以https开头
\z航末锚位 匹配字符串的绝对末尾
m{\.png\z}i 匹配以.png结尾的字符串
\Z锚位 行未锚位,允许后面出现换行符

while (<STDIN>)
{
    print if /\.png\Z/; #\Z匹配行尾的换行符   
}

while (<STDIN>)
{
    chomp; #\z时手动去掉末尾换行符
    print if /\.png\z/;   
}

同时使用\A\Z:
/\A\s*\Z/ 匹配一个空行,但这个空行是包含若干个空白符的
\A \Z \z都是Perl 5里面的正则表达式特性
在Perl 4里面,所用的表示字符串开头锚位的是脱字符(^),用于表示字符结尾锚位的是$

$_ = 'This is a wilma line
barney is on another_line
but this ends in fred
and a final dino line';

if (/fred$/m)
{
    print "OK!\n";
}

请严格选用\A和\z,除非真的需要用到匹配多行文本的情况。

(2) 单词锚位
\b 单词边界锚位 匹配任何单词的首尾
/\bfred\b/ 只可匹配fred,无法匹配frederick,alfred或manfredmann
单词锚位只匹配以\w字符的开头和结尾

非单词边界锚位\B ,匹配所有\b不能匹配的位置
/\bsearch\B/会匹配searches,searching和searched. 但是不匹配search,researching.


(3) 绑定操作符=~
默认情况下,模式匹配的操作对象是$_,绑定操作符告诉Perl,拿右边的模式来匹配左边的字符串,而不是匹配$_
my $some_other = "I dream of betty rubble.";
if ($some_other =~ /\brub/)
{
    print "Aye, there's the rub.\n"
}

print "Do you like Perl? ";
my $likes_perl = (<STDIN> =~ /\byes\b/i); #或者my $likes_perl = <STDIN> =~ /\byes\b/i;
if ($likes_perl)
{
    print "You said earlier that you like Perl,so...\n"
}

(4) 模式中的内插
my $what = "larry"; #也可以从命令行获取:my $what = shift @ARGV;
while (<>)
{
    if (/\A($what)/)
    {
        print "We saw $what in beginning of $_\n";
    }
   
}

(5) 捕获变量
\4: 反向引用的是模式匹配期间得到的结果
$4: 模式匹配结束后对得到的捕获内容的索引
Exam1:
$_ = "Hello there, neighbor";
if (/\s([a-zA-Z]+),/)
{
    print "The word is $1!\n";
}

Result:
The word is there!

Exam2:
$_ = "Hello there, neighbor";
if (/(\S+) (\S+), (\S+)/) #[\s\S] 意思是匹配所有空白字符+非空白字符, 就是指全部字符
{
    print "The word are $1 $2 $3\n";
}

Result:
The word are Hello there neighbor

Exam3:
my $var = 'I think that I will see you in 1000 years';
if ($var =~ /([0-9]+) years/)
{
    print "The \$1 value is $1\n";
}

Result:
The $1 value is 1000

(6) 捕获变量的存续期
捕获变量会一直存活到下次成功匹配为止,也就是说如果下次成功匹配后,就会覆盖之前的捕获变量的值,但是如果下次匹配失败,则
捕获变量的值还保留上一次的值。

(7) 不捕获模式
if (/(bronto)?saurus (steak|burger)/) #bronto只做分组,不做捕获,后面的(steak|burger)才是需要捕获的
{
    print "Fred wants a $2\n"; #只能使用$2,$1已经被(bronto)?占用了
}

解决的办法:
使用不捕获圆括号: ?: 告诉这一对圆括号完全是为了分组存在

my $var = 'brontosaurus BBQ steak';
if ($var =~ /(?:bronto)saurus (?:BBQ)? (steak|burger)/)
{
    print "Fred wants a $1\n"; #还会获取自己喜欢的steak或者burger   
}

(8) 命名捕获
下面举例说明一下问题的来源:
use 5.010;
my $names = 'Fred or Barney';
if ( $names =~ m/(\w) and (\w+)/) #不会匹配成功,因为$names里面用的是or,而匹配模式里面是and
{
    say "I saw $1 and $2";
}

修改1:

use 5.010;
my $names = 'Fred or Barney';
if ( $names =~ m/(\w+) (or|and) (\w+)/) #可以匹配成功,但是$2捕获的值不是我们想要的
{
    say "I saw $1 and $2";
}
输出:
I saw Fred and or

修改2:
use 5.010;
my $names = 'Fred or Barney';
if ( $names =~ m/(\w+) (?:or|and) (\w+)/) #可以匹配成功,但是$2捕获的值是我们想要的
{
    say "I saw $1 and $2";
}

输出:
I saw Fred and Barney

当时如果需要捕获的内容有很多的话,用不匹配模式就不方便了。
为了避免记忆$1之类的数字变量,Perl 5.10增加了对捕获内容直接命名的写法
最终捕获的内容会保存在特殊哈希%+里面:其中的键就是在捕获时用的特殊标签,对应的值是被捕获
的字符串
具体的写法:
(?<LABEL>PATTERN)  #LABEL自行命名

修改4:
use 5.010;
my $names = 'Fred or Barney';
if ( $names =~ m/(?<name2>\w+) (or|and) (?<name1>\w+)/)  # 定义捕获标签?<name1>和?<name2>
{
    say "I saw $+{name1} and $+{name2}"; #根据标签来捕获值
}

输出:
I saw Barney and Fred

在使用捕获标签后,反向引用的用法也随之有所改变。
之前用\1或者\g{1}写法,现在可以用\g{label}
use 5.010;
my $names = 'Fred or Barney Fred';
if ( $names =~ m/(?<name2>\w+) (?:or|and) (?<name1>\w+) \g{name2}/) #将\g{name2}修改为\g{1}也是可以的,只不过扩展性不好。
{
    say "I saw $+{name1} and $+{name2}";
}

输出:
I saw Barney and Fred

#注:我们也可以使用\k<label>来表示反向引用
use 5.010;
my $names = 'Fred or Barney Fred';
if ( $names =~ m/(?<name2>+) (?:or|and) (?<name1>\w+) \k<name2>/)  #使用\k<name2>来代替\g{name2}
{
    say "I saw $+{name1} and $+{name2}";
}


(9) 自动捕获变量
$& : 字符串里实际匹配模式的部分会被自动存进$&里面
$name = "hello here, byebey!";
if ($name =~ /\s(\w+),/)
{
    print "$&\n";
}

Result:
 here, #注意,前面有一个空格
 
注:第一个捕获内容存放在$1中,$&里面保存的是整个匹配区段
匹配区段之前的内容会存到$`里面,而匹配区段之后的内容会存到$'里面。
$name = "hello here, byebey!";
if ($name =~ /\s(\w+),/)
{
    print "\$`:$`\n\$&:$&\n\$':$'\n";
    print "$`$&$'\n"; #三个字段连接起来,得到原来的字符串
}

Result:
$`:hello    #保存匹配区段之前的内容
$&: here,   #保存匹配区段内容
$': byebey! #保存匹配区段之后的内容
hello here, byebey!

特别需要注意的是:
使用自动捕获变量会导致其他正则表达式的速度变慢。
其实可以有变通的方法。比如说将整个模式加上(),然后使用$1来代替$&

如果使用Perl 5.10或以上的版本,修饰符\p只会针对特定的正则表达式开启类似的自动捕获变量。
使用${^PREMATCH} ${^MATCH} ${^POSTMATCH}来代替$` $& $'

$name = "hello here, byebey!";
if ($name =~ /\s(\w+),/p)
{
    print "PREMATCH is ${^PREMATCH}\n";
    print "PREMATCH is ${^MATCH}\n";
    print "PREMATCH is ${^POSTMATCH}\n";
    print "${^PREMATCH}${^MATCH}${^POSTMATCH}";  
}

Result:

PREMATCH is hello
PREMATCH is  here,
PREMATCH is  byebey!
hello here, byebey!

(10) 通用量词
*  :  同{0,}
+  :  同{1,}
?  :  同{0,1}

(11) 正则表达式优先级(高到低)
圆括号     (...) (?:...) (?<LABEL>...)
量词       a* a+ a? a{n,m}
锚位和序列 abc ^ $ \A \b \z \Z
择一竖线   a|b|c
原子       a [abc] \d \1 \g{2}

示例:
while (<>)
{
    chomp;
    if (/\s\z/) #以空格结尾但是不包括换行符
    {
        print "$_#";
    }

}

 

第九章 用正则表达式处理文本
(1) 用s///进行替换
m// 表示模式匹配 实现查找的功能(pattern match)
s///替换操作符实现查找并替换功能(substitution)

$_ = "He's out bowling with Barney tonight.";
s/with (\w+)/against $1's team/;
print "$_\n";
Result:
He's out bowling against Barney's team tonight.

s///返回的是布尔值

(2)  用/g进行全局的替换
一个相当常见的全局替换是去掉连续的空格

$_ = "He's   out   bowling  \twith   Barney   tonight.";
s/\s+/ /g;
print "$_\n";

Result:
He's out bowling with Barney tonight.

s/^\s+//; #将开头的空白替换成空字符串
s/\s+$//; #将结尾的空白替换成空字符串
s/^\s+|\s+$//g; #去除开头和结尾的空白符  这么做会运行的稍慢一点


(3) 不同的定界符
使用没有左右之分的字符:
s#^https://#http://;

使用有左右之分的成对字符,就必须使用两队:一对包住模式,一对包住替换字符串。它们可以不必相同.
s{hello}{OK}
s[hello](OK)
s<hello>#OK#

(4) 可用替换修饰符
除了/g,还可以使用/i /x /s等/
s#wilma#Wilma#gi; #将所有的WilmA或者WILMA等一律替换为Wilma
s{_END_.*}{}s; #将_END_标记和他后面的所有内容都删除掉

(5) 绑定操作符
$file_name =~ s#^.*/##s;

(6) 无损替换
my $hello = "Fred ate 1 rib";
my $copy = $hello;
$copy =~ s/\d+ ribs?/10 ribs/; #上面一步和这一步可以替换为:(my $copy = $hello) =~ s/\d+ ribs?/10 ribs/;
print "\$hello value is '$hello'\n";
print "\$copy value is '$copy'\n";

Result:
$hello value is 'Fred ate 1 rib'
$copy value is 'Fred ate 10 ribs'

Perl 5.014增加了一个/r修饰符(/r)
use 5.014;
my $hello = "Fred ate 1 rib";
my $copy = $hello =~ s/\d+ ribs?/10 ribs/r; #先做替换再做复制
print "\$hello value is '$hello'\n";
print "\$copy value is '$copy'\n";


$_ = "I saw Barney with Fred.";
s/(fred|barney)/\U$1/gi; #将\U转义符其后的所有字符转换成大写的
print "$_\n";

s/(fred|barney)/\L$1/gi;#将\L转义符其后的所有字符转换成小写的
print "$_\n";
Result:
I saw BARNEY with FRED.
I saw barney with fred.

默认情况下,它们会影响之后全部的(替换)字符串,可以用\W关闭大小写转换的功能:
s/(\w+) with (\w+)/\U$2\E with $1/i;
print "$_\n";
Result:
I saw FRED with barney.

如果上面不加\E,则:
$_ = "I saw Barney with Fred.";
s/(\w+) with (\w+)/\U$1 with $2/i;
print "$_\n";
输出结果为:
I saw BARNEY WITH FRED.

使用小写形式(\l与\u)时,只会影响紧跟其后的第一个字符。
$_ = "I saw Barney with fred.";
s/(fred|barney)/\u$1/gi; #将\U转义符其后的所有字符转换成大写的
print "$_\n";

Result:
I saw Barney with Fred.

组合使用:后续全部字符小写,首字母大写
s/(fred|barney)/\u\L$1/ig;

上面的也可以用在双引号里面:
print "\u\Lyou are a \Ugood\E boy!"
Result:
You are a GOOD boy!

(7) split操作符
格式:
my @fields = split /分隔符/, $string;
实例:
my @fields = split /:/, "abc:def:g:h"; #得到("abc", "def", "g", "h")
my @fields = split /:/, "dbc:def::g:h"; #得到("abc", "def", "", "g", "h")
提到一个规则:
split会保留开头处的空字段,却人舍弃结尾处的空字段
my @fields = split /:/, ":::a:b:c:::"; #得到("","","","a","b","c")
my @fields = split /:/, ":::a:b:c:::", -1; #得到("","","","a","b","c","","","")

利用split的/\s+/模式根据空白符分隔字段
$input = "This  is a \t test.\n";
my @args = split /\s+/, $input;
print "@args\n";

Result:
This is a test.

默认split会以空白字符分隔$_中的字符串:
my @fields = split; #等效于 split /\s+/, $_;


(8) join函数
join将片段组合成一个字符串
my $result = join $glue, @pieces; #将join的第一个参数比作胶水
my $x = join ":",4,6,8,10,12;

Result:
4:6:8:10:12

胶水只在片段之间出现
列表至少需要两个参数,否则胶水无法涂进去

my $x = join ":",4,6,8,10,12;
my @values = split ":", $x;
my $z = join "-", @values;
Result:
4-6-8-10-12

注意:join的第一个参数是字符串而不是模式。

(9) 列表上下文中的m//
$_ = "Hello there, neighbor!";
my($first, $second, $third) = /(\S+) (\S+), (\S+)/;


my $text = "Fred dropped a 5 ton granite block on Mr. Slate";
my @wors =($text =~ /([a-z])+/ig);


my $data = "Barney Rubble Fred Flintstone Wilma Flintstone";
my %last_name = ($data =~ /(\w+)\s+(\w+)/g);
my @keys = keys(%last_name);
my @values = values(%last_name);
print "@keys\n";
print "@values\n";
输出:
Wilma Barney Fred
Flintstone Rubble Flintstone

(10) 非贪婪词
$_ = "fred and barney went bowling last night";
/fred.+barney/ #匹配fred后,.+会一直吞下所有的,然后barney匹配不到时,再一个一个字符地吐出,直到barney匹配或不匹配
/fred.+?barney/ #匹配fred后,.+?会吞下最少的字符,即一个字符,如果barney不匹配的话,再多吞一个,直到barney匹配或不匹配

案例:
$_ = "I thought you said Fred and <BOLD>Velma</BOLD>, not <BOLD>Wilma</BOLD>";
要求去掉:<BOLD>和</BOLD>
s#<BOLD>(.*)</BOLD>#$1#g; #会匹配第一个和最后一个<BOLD> </BOLD> 不符合要求 输出:I thought you said Fred and Velma</BOLD>, not <BOLD>Wilma
s#<BOLD>(.*?)</BOLD>#$1#g; #先匹配第一个和第二个,然后删除,再匹配第三个和第三个,再删除 输出:I thought you said Fred and Velma, not Wilma

非贪婪的版本:
+?
*?
{5,9}?
?? #虽然还是会匹配一次或者零次,但是优先考虑零次。

(11) 匹配多行

注:^和$是整个字符串的开头和结尾的锚位。
当模式加上/m修饰符后,就可以用它们来匹配字符串内的每一行。
$_ = "I'm much better\nthan Barney is \nat bowling,\nWilma.\n";
print "$_\n";
print "Found 'wilma' at start of line\n" if /^wilma\b/im; #OK

实例:
use strict;
use POSIX; #为了使用strftime子程序
my $start_time = strftime("%Y-%m-%d %H:%M:%S",localtime);
print "$start_time\n";
my $filename = "D:\\many_lines.txt";
open FILE, $filename
    or die "Can't open '$filename': $!";
my $lines = join '', <FILE>;
print "$lines\n";
$lines =~ s/^/$filename: /mg; #如果没有/m的话,只在首行加入
print "$lines\n";

my $end_time = strftime("%Y-%m-%d %H:%M:%S",localtime);
print "$end_time\n";

Result:
2012-11-12 13:53:21
A Tom
B is
C a
D good
E boy.
D:\many_lines.txt: A Tom
D:\many_lines.txt: B is
D:\many_lines.txt: C a
D:\many_lines.txt: D good
D:\many_lines.txt: E boy.
2012-11-12 13:53:21


编写Perl脚本,修改文件中的指定内容:
[root@etl10 scott]# cat name.txt #需要修改内容
Name: NIOS
Age: 3 years
phone: 10086
Code: 210000
Time: 2012-11-12 14:04:03

[root@etl10 scott]# cat test.pl #修改文件的脚本
#!/usr/bin/env perl
use 5.010;
chomp(my $time = `date`); #或use POSIX;  my $start_time = strftime("%Y-%m-%d %H:%M:%S",localtime);
$^I = ".bak";
while (<>)
{
    s/Code.*/Code: 210002/g;
    s/Name.*/Name: BICP/g;
    s/age.*\n//gi;
    s/Time.*/Time: $time/g;
    print;
}

[root@etl10 scott]# perl test.pl name.txt  #执行修改
[root@etl10 scott]# cat name.txt #修改过后的文件内容
Name: BICP
phone: 10086
Code: 210002
Time: Tue Nov 12 14:10:11 CST 2013
[root@etl10 scott]# cat name.txt.bak #修改之前保存的内容 可以模仿Linux操作系统,将$^I = "~",这样备份的文件名为name.txt~
Name: NIOS
Age: 3 years
phone: 10086
Code: 210000
Time: 2012-11-12 14:04:03


(12) 从命令行直接输入
perl -p -i.bak -w -e 's/Randall/Randal/g' fred*.dat
-p : 可以让perl自动生成一小段程序,看起来如下:
while (<>)
{
    print;
}
如果用-n替代-p,则可以自动将print去掉
-i : 将$^I设为".bak",如果不想备份,可以直接写-i
-w : 开启告警功能
-e : 后面跟可执行程序代码

上面代码等价于:
use warnings;
$^I = ".bak";
while (<>)
{
    s/Randall/Randal/g;
    print;
}

实例:
$^I = "~";
while (<>)
{
    if (/\A#!/)
    {
         print $_;
         $_ .= "## Copyright (C) 20XX by YOurs Truly\n";
    }
    print; #不能没有
}

在#!开头的行下面添加一行内容:## Copyright (C) 20XX by YOurs Truly

你可能感兴趣的:(Perl笔记3)