本文是针对没有Perl基础,但想用perl一行式命令取代grep/awk/sed的人,用于速学Perl基础知识。html
Perl一行式系列文章:Perl一行式程序正则表达式
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"会自动启用一些高版本的功能。数据结构
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
须要解释下上面的几行语句。
字符串使用单引号或双引号包围,此外,Perl中也有反引号,这3种引用和shell中的单、双、反引号相似。:
$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中的自增、自减操做。
在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
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。
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
主要有keys()、values()、exists()和delete()。
keys %hash
values %hash
exists $hash{KEY}
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结构,可使用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
在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中,真假的判断很简单:
注意,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:
对于<=>
,若是比较的双方有一方不是数值,该操做符将返回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语句 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){} # 假
语法:
while(CONDITION){ commands; } until(CONDITION){ commands; }
Perl的until和其它某些语言的until循环有所不一样,Perl的until循环,内部的commands主体可能一次也不会执行,由于Perl会先进行条件判断,当条件为假时就执行,若是第一次判断就为真,则直接退出until。
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个部分均可以省略,但分号不能省略:
例以下面将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用来遍历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;
改写的方式几个注意点:
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语句块的返回值是最后一个执行的语句的返回值。
例如,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{}
是语句,是被执行的语句块,也有本身的做用域范围。
熟悉sed的人确定很容易理解这里redo和continue的做用。sed默认状况下会输出每一行被处理后的内容,这是由于它有一个和这里continue同样的逻辑,在perl命令的"-p"选项也同样,到时候会解释这个continue的逻辑。sed的"-D"选项则是处理后当即回到sed表达式的顶端再次对模式空间进行处理,直到模式空间没有内容,这实现的是redo的逻辑。
Perl像awk同样,也有BEGIN和END语句块,功能和awk是同样的。实际上Perl除了BEGIN/END,还有CHECK、INIT和UNITCHECK语句块,不过对于一行式Perl程序,BEGIN/END就足够了。
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
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";'
第二种和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的变量、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是如何读、写数据的。
<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//
/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