perl学习笔记

Unix&Perl
Perl是什麽东西呢?Manual上是这样写的:Practical Extractionand Report Language它原始的目的就是用来取代UNIX原有的sed/awk与shell script的组合,用来汇集资讯,产生报表的一个工具语言(程式)。不过随着版本的改进,功能越来越强,现在的功能已经超乎原先设计时的想像,几乎任何事都可以做到,也变成每一部UNIX工作站必备的标准工具了。
 
#!/usr/local/bin/perl
乍看之下是释,其实它大有作用。在古早的UNIX系统里面,可执行档分作两种,一种是包含机器指令的二进位档,系统可以直接载入执行;另外一种叫做script档,也就是包含一些shell命令的普通文字档:UNIX一遇到这种文字档就会自动载入/bin/sh去解译与执行它。不过随着UNIX的发展,shell越来越多,实在不晓得script档是用哪种shell写的。所以就发展出一个机制,在每个script档的第一行(前面不可以有空白与空白行)写个#! ,然後接着真正要负责处理这个script的程式档路径,就可以让UNIX知道要用那个程式来处理了。另外,顺便唆一下,这个路径後面还可以再加一个参数(one and only one),如下:
#!/usr/local/bin/perl -f 对
#!/usr/local/bin/perl -pi.bak  对
#!perl –f -pi.bak 不对 -> -pi.bak会被忽略。
反正写超过一个就会被忽略就对了。
第二章 标量数据
所有数字本质上都是用一种格式。形式上可将数据注明为整型(17或342)和浮点数(带小数点的实数3.14),但本质上,Perl只用双精度浮点值。 程序中的整型常量被作等效的浮点值来处理。
浮点实量:所谓实量(或称常量)是PERL程序中用文本表示值的一种方法。
最短的字符串是没有字符,最长的字符串能把你的内存填满。
字符串,如数字相同,字符串也用实量来表示。
实量字符串有两种格式:单引字符串和双引字符串。
单引字符串中的/n并不被当作换行符(换行就是格式上的换行),而被认作字符/和n.。只有’//’表示 /;’/’’表示/’。
双引字符串中的反斜扛转义:
结构
含义
结构
含义
/n
换行符
/”
双引号
/f
换页符
/L
以后所有字母小写直到/E
/r
回车
/l
下一字母大写
/b
回退
/u
下一字母大写
/t
水平制表符
/U
以后所有字母大写直到/E
/v
垂直制表符
/E
结束/L或/U
/a
响铃
 
 
/e
Esc
 
 
/007
任一八进制ASCII值(这里007=bell)
 
 
/X7F
任一十六进制ASCII值(这里7f=delete)
 
 
/cC
任一“控制”字符(这里是控制字符C)
 
 
/ /
反斜杠
 
 
数字和字符串的比较运算符
比较
数字 (比较数字本身的大小)
字符串 ( 比较的是字符串中字符的 ASCII )
等于
=
eq
不等于
!=
Ne
小于
Lt
大于
Gt
小于等于
<=
le
大于等于
>=
Ge
大于等于小于
<=>
Cmp
注意数字和字符串比较都正好与UNIX test命令相反。该命令对数字比较采用-eq,而字符串比较用=.
 
 
运算符的相关性和优先级(从最低到最高)
序号
相关性
运算符
1
“list”运算符
2
,(逗号)顺序计值符
3
+=
4
?: (三态if/then/else 运算符)
5
.. (域运算符,列表构造器)
6
||
7
&&
8
|^
9
&
10
= =! = <=> eq ne cmp (“等于”运算符)
11
< <= > >= lt le gt ge (“不等于”运算符)
12
已命名一元运算符
13
-r及其它(文件测试运算符)
14
<< >>位移
15
+ - .(加,减,串连接)
16
* / % x(乘,除,取模,串复制)
17
= ~ ! ~ 匹配,不匹配
18
* * ( 乘幂)
19
! - (逻辑非,按位非,数字非)
20
++ --(自动加,自动减)
 
字符串的标量变量插入值
为了防止变量被值替换,你必须修改字符串的变量部分,使它以单引号引起,或在美元符号前加反斜杠,它起到的作用是 屏蔽掉了美元符号的特殊标记作用
字符串中的变量表示法:$a=”It’s ${fred}day”;    $b=”It’s $fred”.”day”;   $c=”It’s”.$fred.”day”;
 
 
第三章 数组及列表数据
1.         数组赋值
如果把一个标量值赋给数组变量,则这个标量值就成为数组中的单独元素。
如果赋值号两边的表中元素数目不等,则任何多出的值都被截去,任何多出的变量都被赋成undef。
出现在数组实量列表中的数组变量必须位于表尾,因为所有剩余值。
如果把数组变量赋给标量变量,则周期给标量变量的就是数组的长度。
case:($a,@fred)=($a.,$b,$c)
2.         $fred[0]     @fred[0,1]
3.         超出数组范围的元素赋值可使数组自动增加长度(除指定元素外,如果还有超出范围的元素,则这些元素的值均被赋成undef)。
4.         以用$#fred来得到数组@fred末尾元素的索引值。
1) Push(@mylist, $newvalue);
$oldvalue=pop(@mylist);删除最后一个元素;
 
2) @fred=unshift(@fred,2.3.4); @fred=(2,3,4, @fred)
$x=shift(@fred); $x=2
3) reverse ( )
4) sort( )
5) chop( )
5.           数组变量插入:注意在简单标量引用后面加左方括号,那你需要将它们分开,以免被误认为是数组引用。
6.           数组名称在标量环境中使用时是该数组当前的元素的序号。只要该数组非空,序号就不是0,也就为真。这是非常普通的PERL的习惯用法:“运行直到该数为空”。
7.           列表(list)指的是标量的有序集会,而数组(array)则是存储列表的变量。更精确的说,列表指的是数据,而数组指的是变量。列表值可以不放在数组里,但每个数组变量都一定包含一个列表。列表直接量(程序中用来表示列表值的方法)是圆括号内一串以逗号分隔的值,这些值构成了列表中的元素。(1,2,3) #包含1,2,3这3个数字的列表(1...100) #100个整数所构成的列表
8.         qw简写
在perl程序里,经常需要建立简单的单词列表(如同前面的范例)。这时只需要用qw简写,便可以省去键入一堆额外引号的时间;
qw / fred barney betty wilma dino /
其实perl允许你以任何标点符号为界定符。例如:
qw # fred barney betty wilma dino #
第五章 关联数组
1.         添加关联数组元素
 创建一个关联数组元素最简单的方法是赋值,如语句$fruit{"bananas"} = 1; 把1赋给关联数组%fruit下标为bananas的元素,如果该元素不存在,则被创建,如果数组%fruit从未使用过,也被创建。
A.一定要使用 delete函数来删除关联数组的元素,这是唯一的方法。
B.一定不要对关联数组使用内嵌函数push、pop、shift及splice,因为其元素位置是随机的。
2.         创建关联数组
注:用列表给关联数组赋值时,Perl5允许使用"=>"或","来分隔下标与值,用"=>"可读性更好些,上面语句等效于:%fruit = ("apples"=>17,"bananas"=>9,"oranges"=>"none");
 
与列表一样,也可以通过数组变量创建关联数组,当然,其元素数目应该为偶数。
 
最后,关联数组可以通过返回值为列表的内嵌函数或用户定义的子程序来创建,下例中把 split()函数的返回值--一个列表--赋给一个关联数组变量。
1: #!/usr/local/bin/perl
2:
3: $inputline = ;
4: $inputline =~ s/^/s+|/s+/n$//g;
5: %fruit = split(//s+/, $inputline);
6: print ("Number of bananas: $fruit{/"bananas/"}/n");
    运行结果如下:
oranges 5 apples 7 bananas 11 cherries 6
Number of bananas: 11
   在展开的表示中键-值对的顺序可以是任意的,并且不受用户控制。同一个元素(使用相同的关键字)的引用,返回其值。
3.         关联数组操作符
Keys()操作符@a=Keys(%arrayname); 
在标量环境中,keys( )操作符返回关联数组中元素(键-值对)的个数。可利用这来判断关联数组是否为空。
Values( )操作符 @a=values(%arrayname);
each( )操作符 @a=each(%arrayname); #返回键-值,对同一数组再一次使用此操作,可返回下一个键-值对,直到处理完所有元素。
delete( )操作符,删除键-值对。
第四章 控制结构 & 第九章 复杂控制结构
1.         控制语句VS 表达式修改器VS符号控制器( &&,||,?:
 
序号
控制语句
表达式修改器
符号控制器( &&,||,?:
1
if(this){that}
that if this
this&&that
 
if(exp1){ exp2}else(exp3)
 
exp1?exp2:exp3
2
unless(this){that}
that unless this
this || that
3
while(this){that}
that while this
 
4
until(this){that}
that until this
 
3
for(i=1;$i<=10;$i++){that}
 
 
4
foreach$i(@some_list){that}
 
 
 
2.         foreach控制结构
   能够一次处理整个数组或列表相当的方便,所以perl提供了一种控制结构来做这件事,foreach循环会逐项处理列表的值,每次对一个值执行循环里的操作:
foreach $rock (qw/ bedrock slate lava /){
print "one rock is $rock. ";
}
foreach中的标量变量,当循环退出时标量自动恢复为最初的值,即数组变量是循环内的局部变量。
3.         $_变量名,是很多Perl操作符使用的缺省值,故不指定一般标量变量,就是用$_。
4.         循环控制
last操作符会立即中止循环的执行;
next操作符会跳到当前循环酷爱的底端,在next之后,程序将会继续执行循环的下次迭代;
redo操作符会跳回当前循环块的顶端,而不经过任何测试条件;
注意这些循环控制,只能跳出;
注意标号块的运用。
第六章 基本I/O
从STDIN输入
在标量标量环境中,返回随后输入的那一行内容(即直到一换行符或到你所设置的 $/ )
在数组环境中读取剩下的所有行。。。。。。
可以用循环来实现一次读入多行,分别处理各行的办法。当循环检查只包含输入操作符(类似<…>)时,Perl自动把读入的行拷贝到$_变量。While() 同while($_=)
从<>操作符输入
   不同的是< >操作符可以从申请Perl程序的命令行所指定的文件中读取数据。例如,如果有一程序名为Kitty,并使用命令行:Kitty file1 file2 file3。
Kitty有些像cat,它把指定文件中的所有行都依次送至标准输入。如果不在命令行中指定任何文件名,则< >操作符就自动从标准输入中读取数据。从技术上讲,< >操作符并不直接使用命令行参数——它使用@ARGV数组。这是个特殊的数组,它被Perl解释器预置为 命令行参数表。在Perl取得命令行开关之后,各命令行参数就成为数组@ARGV中的元素。你可以根据需要来使用此数组。( Perl 的标准库中包含类似于 get opt 的程序,用于解释 Perl 程序中的命令行参数。)你甚至还可以在程序中设置这个数组。。。
@ARGV=(“aaa”,”bbb”,”ccc”);
While(< >){
Print “this line is:$_”;
}
 
STDOUT输出:Print、printf

 
7 正则表达式
[正则表达式是一种模式(模板),用于与一个字符串相匹配。用于查询和替换。]
字符与自身匹配的单个字符。
 
字符”.”,与除换行符(/n)之外的任何单字符相匹配。 即非换行符
 
模式匹配“字符类”由一对开闭方括号和括号内的字符列表组成。根据匹配模式,这些字符中的一个且仅有一个将与字符串中的相应部分进行匹配操作。
/[abcde]/:与小写字母表前五个字母中的一个相匹配;
/[abiouAEIOU]/:与小写或大写的五个元音字母相匹配;
/[0-9/-]/:匹配0-9,或减号。
/[]a]/或/[a/]]/:右方括号要么作为列表中的第一个字符;要么在其前加一反斜杠。
/d(数字) [0-9]
/D(非数字) [^0-9]
/w(字符) [a-z A-Z0-9_ ]
/W(非字符) [^a-z A-Z0-9_ ]
/s(空格) /r/t/n/f]
/S(非空格) [^a-z A-Z0-9_ ]
序列
 
倍增器,有多个时,以“左优先”。
限定符 *
后面紧跟 零个或多个该字符(字符类);
限定符 ?
后面紧跟 零个或一个该字符;
限定符 +
 
 
后面紧跟一个或多个该字符;
用作记忆功能的圆括号
限定符 ( )
用反斜杠加一个整数来检索已记忆的字符串部分。
 
限定符 {n}
n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
限定符 {n,}
n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
限定符 {n,m}
m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。
 
 
定位符
$`:$&:$'/n
/b 表示单词的末尾。例:"er/b""never""er"一致,但和"verb""er"不一致。
/B
表示非单词的末尾。
$
c$表示以c为结尾的字符串;如果是变量可以将$放在其前面如:$path =~/$$vob/
^:在字符的前面,与含以该字符打头的字符串相关匹配。如^a表示以a为开头的字符串。
交替
|
匹配操作符
 
忽略大小写
最后一个斜杠后加上一个小i /i
选择不同的目标
$a=”hello.world”;
$a=~/^he/ 
使用不同分界
当正则表达式中,Perl允许用户制定不同的分界符。作为分界符的字符必须为非字符或数字,并在其前加上m,然后就可在模式的后面直接使用此分界符。
 
 
插入变量
$sentence=~/$what/
 
 
特殊的只读变量
$_: 默认的输入/输出和格式匹配空间
$&:表示字符串与正则表达式相匹配的部分;
$`:表示字符串中相匹配之前的部分;
$’ :表示字符串中相匹配之后的部分;
[$`在上个格式匹配信息前的字符串;
$’ 在上个格式匹配信息后的字符串 ]
$+ 与上个正则表达式搜索格式匹配的最后一个括号
$ 含有与上个匹配正则表达式对应括号结果
Exp:
$_=”this is a test”;
/(/w+)/W+(/w)/#匹配前两个单词
#$1现在为”this”,而$2为”is”。
 
C:/>perl -e "print $^O;";
MSWin32
注意,这里这个特殊变量$^O 最后一个字符是字母O,且大写。
如果是在linux下的话,结果就不是MSWin32了,而是Linux ;
这是一个判断环境的变量,简单实用。
perl的内置特殊变量还有很多的,例如常用的还有@_、@ARGV、$ARGV、%INC、%ENV等等,有很多,不可能一一列举了,这里给出一个比较有总结性的列表,是chinaunix上perl社区的一个网友总结的,还算比较全:
$- 当前页可打印的行数,属于Perl格式系统的一部分
$! 根据上下文内容返回错误号或者错误串
$” 列表分隔符
$# 打印数字时默认的数字输出格式
$$ Perl解释器的进程ID
$% 当前输出通道的当前页号
$& 与上个格式匹配的字符串
$( 当前进程的组ID
$) 当前进程的有效组ID
$* 设置1表示处理多行格式.现在多以/s和/m修饰符取代之.
$, 当前输出字段分隔符
$. 上次阅读的文件的当前输入行号
$/ 当前输入记录分隔符,默认情况是新行
$: 字符设置,此后的字符串将被分开,以填充连续的字段.
$; 在仿真多维数组时使用的分隔符.
$? 返回上一个外部命令的状态
$@ Perl解释器从eval语句返回的错误消息
$[ 数组中第一个元素的索引号
$/ 当前输出记录的分隔符
$] Perl解释器的子版本号
$^ 当前通道最上面的页面输出格式名字
$^A 打印前用于保存格式化数据的变量
$^D 调试标志的值
$^E 在非UNIX环境中的操作系统扩展错误信息
$^F 最大的文件捆述符数值
$^H 由编译器激活的语法检查状态
$^I 内置控制编辑器的值
$^L 发送到输出通道的走纸换页符
$^M 备用内存池的大小
$^O 操作系统名
$^P 指定当前调试值的内部变量
$^R 正则表达式块的上次求值结果
$^S 当前解释器状态
$^T 从新世纪开始算起,脚步本以秒计算的开始运行的时间
$^W 警告开关的当前值
$^X Perl二进制可执行代码的名字
$_ 默认的输入/输出和格式匹配空间
$| 控制对当前选择的输出文件句柄的缓冲
$~ 当前报告格式的名字
$` 在上个格式匹配信息前的字符串
$’ 在上个格式匹配信息后的字符串
$+ 与上个正则表达式搜索格式匹配的最后一个括号
$< 当前执行解释器的用户的真实ID
$ 含有与上个匹配正则表达式对应括号结果
$= 当前页面可打印行的数目
$> 当前进程的有效用户ID
包含正在执行的脚本的文件名
$ARGV 从默认的文件句柄中读取时的当前文件名
%ENV 环境变量列表
%INC 通过do或require包含的文件列表
%SIG 信号列表及其处理方式
@_ 传给子程序的参数列表
@ARGV 传给脚本的命令行参数列表
@INC 在导入模块时需要搜索的目录列表
$-[0]和$+[0] 代表当前匹配的正则表达式在被匹配的字符串中的起始和终止的位置 。
 
 
替换
s/旧的正则表达式/新串/;
如果需要匹配所有匹配的字符串而不仅仅是第一个,只需在操作符的后面加上“g”。
和匹配操作符一样,如果不方便使用斜杠作为分界符,可以选择其它符号替换匹配操作符,只需使用同一字符三次即可。
$_=”this is a test”;
s/(w+)/<$1>/g; # $_现在为””;
 
 
分割转组操作符
Split(/分隔符/, 变量) 
默认模式:@words=split;#与@words=split(//s+/,$_)相同 
注:如果需要引用空格作为分界符最好用/ +/ 或//s+/
Join()操作符:$a=join($glue,@list); $b=join(“:”,@fields);
优先级:由高到低 括号() -> 倍增器 +×?{ m,n -> 序列与定位 abc ^ $ /b/B   -> 交替 |
如果一个表达式中有两个倍增器,则优先原则扩展为“左优先”。例如:$_=”a***c*******c***d”;
 
非回溯 / 回溯
非回溯(?>Exp):该组匹配后,其匹配的字符不能通过回溯用于后面的表达式的匹配。
举个例子来说明吧
源字符串:www.csdn.net
正则一:/w+/.(.*)/./w+
正则二:/w+/.(?>.*)/./w+
正则一是可以匹配成功的,由(.*)匹配csdn,而正则二是匹配不成功的
因为在正则一中,(.*)是贪婪模式,它要一直匹配到字符串末尾,也就是csdn.net,这时发现/./w+没有匹配到内容,而你需要明白的一点是,正则匹配的一个大前提,就是尽可能的匹配成功,这时它将会采用回溯匹配,所谓回溯,就是把(.*)匹配的结果往回推也就是由(.*)匹配csdn,而由/./w+来匹配.net,以使整个表达式匹配成功
正则二之所以匹配不成功,是因为它是非回溯匹配,当(?>.*)匹配到末尾时,它发现没有匹配成功,因为它是非回溯的,不允许采用回溯来匹配/./w+,所以整个表达式也就匹配不成功了
弱弱的说,用正则半年多了,几乎天天都在用,但是从来没用过非回溯匹配
同时应注意,回溯匹配是很浪费资源,效率很低的一种匹配方式,要尽量避免,如上式改为
/w+/.([^.]*)/./w+
就可以避免回溯匹配方式了
 
8 章 函数
1、用户函数,即子程序(sub):sub 子程序名{子程序名体}。
2、在子程序名前加上符号“&”,可以在任一表达式内调用子程序。
3、子程序的返回值是每次调用过程中子程序体的最后一个表达式的计算值。
4、@_作为存放子程序参数的参数变量。
$_[0]是数组@_的第一个元素,$_[0]变量值与$_变量的值(标量变量)无实际联系。
对于子程序来说@_变量是局部变量;如果@_有一个全程值,它将在调用子程序之前存贮到其它地方,并在由子程序返回时恢复原来的值。这也就意味着子程序可以将参数传递给另外一个程序,而不必担丢失其@_变量
5、局部变量 local( ):local()的果是可赋值的列表,也就是说它可以用在数组赋值操作符的左侧。对于一个新建立的变理,都可以给该列表赋初值。如果将local()放在循环体内部,徨体的每次运算都会产生一个全新的变量值,但这会浪费内存且无意义,注意将local()放在子程序定义的开始部分。
 
10 章文件句柄及文件测试 & 12 章目录访问 & 13 章节文件及目录处理
1 、文件句柄( file handle 是是perl程序为perl程序和外部世界之间提供I/O连接的名称。
文件句柄STDIN/STDOUT/STDERR,自动打开由程序的父进程建立的文件或设备。
Open(file handle “file name”) 该调用为读文件打开文件句柄(即打开文件用于输入)
Open(file handle “>file name”) 该调用为写文件打开文件句柄(即打开文件用于输出)
Close(fle hanel)
Open(file handle “a”)|| die”sorry,I could not create a”
Die() 操作符在可先的括号里带有一列表,以标准错误输出的方式输出该表(像print),然后以非零的UNIX退出状态结束该PERL进程序。死掉的信息(die 所带的参数)自动附带有 perl程序名及行数,你可以轻易地鉴别出程序中哪一个 die应对提前退出负责。如果你不喜欢显示行数或文件名,要确保死掉时显示的文本在结束时另起一行。比如: die”you gravy-sucking pigs”  打印出文件及行数,而die”you gravy-sucking pigs/n”不行。
小于字符(<) 来表示读 : 如果文件不存在, open() 将返回 false 此时,你可以读文件句柄,但不可以写。
  
  大于字符表示写 : 如果文件不存在,就会被创建。如果文件存在,文件被清除, 以前的数据将会丢失。你可以写入文件句柄,但不可以读入。
 两个大于符号 (>>) 表示添加模式:如果文件不存在,可以用来创建新文件,如果文 件存在,该模式并不会清除原来的数据。
注意“+<” “+>” 的区别,两者都可以可读可写。前者为非破坏性写, 后者为破坏性写。
通过 “+<” 模式,你可以既可以读文件,又可以写文件。你可以通过 tell() 函数在文件内部移动,通过 seek() 函数进行定位。如果文件不存在,就会被创建。 如果文件已经存在,原来的数据不会被清除。
  如果你打算清除原来的文件内容,或者自己调用 truncate() 函数,或者使 “+>” 模式。
2、使用文件句柄
Open(IN,$a)||die”cannot open $a for reading”;#文件句柄IN为读取文件而打开。
Open(OUT,>$b) ||die”cannot create $b”;文件句柄OUT为写入或追加文件$b而打开。
While(){#使用行的读取方式,从文件$a中读取一行赋给$_
Print OUT $_;}#将该行打印到文件$b中
Close(IN);
Close(OUT);
-x文件测试及其意义
序号
文件测试
意义
 
-r
文件或目录可读
 
-w
文件或目录可写
 
-x
文件或目录可执行
 
-o
文件或目录归用户所有
 
-R
文件或目录对真正的用户可读,而不是有效用户(不同于 –r 对setuid程序)
 
-W
文件或目录对真正的用户可写,而不是有效用户(不同于 –w 对setuid程序)
 
-X
文件或目录对真正的用户可执行,而不是有效用户(不同于 –x 对setuid程序)
 
-O
文件或目录归f真正的用户所有,而不是有效用户(不用于-o 对setuid程序)
 
-e
文件或目录存在
 
-z
文件存在且大小为0(目录从不为空)
 
-s
文件或目录存在且大小不为0(数值为字节数)
 
-f
登录项为纯文件
 
-d
登录项为路径
 
-l
登录项为symlink
 
-S
登录项为 socket
 
-p
登录项为已命名的pipe(fifo,即先进先出)
 
-b
登录项为特殊块的文件(像可装配的磁盘)
 
-c
登录项为特殊字符的文件(像I/O设备)
 
-u
文件或目录是设备用户ID号
 
-g
文件或目录是设备用户组ID号
 
-k
文件或目录有 sticky位设置
 
-t
文件句柄中的isatty()为真
 
-T
文件是文本格式
 
-B
文件是二进制格式
 
-M
以天为单位修改年龄
 
-A
以天为单位访问年龄
 
-C
以天为单位 inode 修改年龄
这些操作符的参数文件句柄或文件名。默认的操作数是$_变量中定义的文件名。
Foreach(@list){
Print “$_ is readable /n” if –r
}
Stat()、Lstat( )的操作数是一个文件句柄,或者相当于文件名的表达式。返回值要么是undef,指示stat失败,要么是有13个值的数组。默认操作数是$_。
对于符号链接的名字申请stat()返回符号链接所批之处的信息,而不 关于符链接本身信息(除非链接本所指的是没有可访问的内容)Lstat( )返回链接本身的信息。
 
_文件句柄
每次谈起stat() –r –w 或程序中的任何事物,perl都要退出系统申请一个该文件的sat缓冲区(由stat系统调用得到的返回缓冲区)。即一个文件是否既可读又可写,你必须为同一信息申请系统两次。
用_文件句柄,stat lstat作为操作数告诉 perl使用前一次文件测试挂起的内存,而不必再次退出操作系统。
if(-r $filevar &&-w_){
Print “$filevar is both readable and writabel /n”
}
第11章           格式
1、格式定了常量部分(每列的开头、标签、相应的正文或其它)变量部分(报告中的数据)。
2、使用格式需要做三件事:
1).定义格式。
2).提取数据,将它打印到格式(字段)的变量部分中。
3).申请格式。
通常是一旦第一步完(也可用eval操作符在运行时创建格式),另两步已重复执行了。
3、 定义格式
field line 可以是组合文本行也可是字段句柄,如果是字段句柄其下一行指示相应变量数值。
模板中的空格(换行、制表符)被识别;数值行中的空格被忽略。
Format format name=
Fieldline
Value,value
Fieldline
Vlaue
.
4、 申请格式
可使用write操作符申请格式。该操作符取出文件句柄的名字,产生文本以便该文件句柄使用当前的格式。文件句柄当前的格式默认为一同名的格式。
Format ADDRESSLABEL=
= = = = = = = = = = = = = = = = = = = = = =
|@<<<<<<<<<<<<<<<<<<<<<<|
$name
|@<<<<<<<<<<<<<<<<<<<<<<|
$address
|@<<<<<<<<<<<<<@<@<<<<|
$city,$state,$zip
= = = = = = = = = = = = = = = = = = = = = =
Open (ADDRESSLABEL,“>labels-to-print“)|| die“can’t create”;
Open(ADDRESSES,”address”)|| die “can’t open”;
while (
){
chop;
($name,$address,$city,$state,$zip)=split(/:/);#提取全局变量
Write ADDRESSLABEL;#发送输出 此ADDRESSLABEL为文件句柄。
字段句柄详解
字段句柄类型
字段类型符(以@或^开始,紧随之的指示字段的类型,字符数(含@或^)为字段的宽度。)
 
 
文本字段
’<’、’|’、’>’(分别表示左对齐;中间对齐;右对齐。)
 
数字字段
以@开始,紧跟着#及可选的小数点。
 
多重字段
@*:表示将结果变量值按其换行符换行输出。
 
填充字段
 
以^ 开始,紧跟着’<’、’|’、’>’(分别表示左对齐;中间对齐;右对齐。)
如果文本少于三行,将出现空行,可以在前面添加 ~ 符号自动去除空行。
如果文本多于三行,将被截断,可以在前面添加 ~~ 符号自动增长并自动去除空行。
 
使用select( ) 改变文件句柄
Print(write或其它操作),其缺省值为“当前选定的文件句柄”,当前选定的文件句柄以STDOUT作为开始,使得在标准输出上打印很容易。
print “hello world /n” #类似于print STDOUT “hello world /n”
Select(LOGFILE); #选择新的文件句柄
print “hello world /n”  #类似于print LOGFILE “hello world /n”
select (STDOUT); #再次选择 STDOUT
print”back to stdout/n”;#返回标准输出
oldhandle=select(LOGFILE);#选择新的句柄,同时保存旧的句柄。
print “ddd /n”;
select($oldhandle);#恢复前一个句柄
改变格式名
文件句柄的默认格式名就是该文件句柄本身,用$~改变其格式名。
为REPORT文件句柄设置格式名为SUMMARY:
$oldhandle=select(REPORT);
$~=”SUMMARY”;
Select($oldhandle);
 
Write REPORT; #使用SUMMARY格式;稍后又恢复它。
页顶格式
对于特定的文件句柄来说默认的页顶格式名是该文件句柄名称加_TOP(只能大写)。
$^ 保留当前选定的文件句柄 的页顶格式名并且是可以读写的。因此可以用这个变量检查页顶格式名或改变它。
$% 为页顶格式被特定文件句柄调用的次数,因此可以用这个变量记住相应的页码。
$ = 用于设置页面长度 默认为 60行。
$ - 为write用实际输出的行数递减剩下的行数值。当该数到达0时,页顶格式被调用并且$ -从$ =中拷贝出来。(除了write,perl不能记录任何操作的行数。)
改变页长度
如何将LOGFILE文件句柄改变成只有30行的页面:
$old=select(LOGFILE);
$ = =30;
Select($old);
 
改变页面中的位置
给STDOUT发送附加的一行:
Write;
…;
Print “An extra line …OOPS!/n”; 转到STDOUT
$ - --; 递减$ - 指示没有写的那行转到STDOUT
…;
Write;#照常运行,记录附加的行。
 
14 进程管理
补充内容:
 
注:shell的作用之一:program->compiler->二进制代码
                    命令行 ->command.com(DOS)/shell(UNIX/LINUX)->机器码
 
什么叫进程?进程同程序有什么区别?
A:进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个
进程。显然,程序是死的(静态的),进程是活的(动态的)。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程进程是操作系统进行资源分配的单位。在Windows下,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
那进程与线程的区别到底是什么?进程是执行程序的实例。例如,当你运行记事本程序(Nodepad)时,你就创建了一个用来容纳组成Notepad.exe的代码及其所需调用动态链接库的进程。每个进程均运行在其专用且受保护的地址空间内。因此,如果你同时运行记事本的两个拷贝,该程序正在使用的数据在各自实例中是彼此独立的。在记事本的一个拷贝中将无法看到该程序的第二个实例打开的数据。
以沙箱为例进行阐述。一个进程就好比一个沙箱。线程就如同沙箱中的孩子们。孩子们在沙箱子中跑来跑去,并且可能将沙子攘到别的孩子眼中,他们会互相踢打或撕咬。但是,这些沙箱略有不同之处就在于每个沙箱完全由墙壁和顶棚封闭起来,无论箱中的孩子如何狠命地攘沙,他们也不会影响到其它沙箱中的其他孩子。因此,每个进程就象一个被保护起来的沙箱。未经许可,无人可以进出。
实际上线程运行而进程不运行。两个进程彼此获得专用数据或内存的唯一途径就是通过协议来共享内存块。这是一种协作策略。下面让我们分析一下任务管理器里的进程选项卡。这里的进程是指一系列进程,这些进程是由它们所运行的可执行程序实例来识别的,这就是进程选项卡中的第一列给出了映射名称的原因。请注意,这里并没有进程名称列。进程并不拥有独立于其所归属实例的映射名称。换言之,如果你运行5个记事本拷贝,你将会看到5个称为Notepad.exe的进程。
一、 System( )
System(“date>right_now”)&&die”cannot create right_now”;
Sysytem 操作符可以包含用分号(;)隔开的多个命令。以&结尾的进程可以立即发送给shell而不用等待。
$where=”who-out.”.++$|;
System(“(date;who)>$where&);
在这种情况下,System 操作符返回值就是shell 返回值,它可以说明后台处理是否成功,但不能说明执行date 命令和who 命令是否成功 。因为带双引号的字符串是个变量,所以$where 在shell 见到它之前就被自身的实际值替换掉了,如果想引用shell 变量$where ,就必须去掉美元符号或者使用带引号的字符串。
System操作符不但可以使用单个参数,也能使用多个参数。在这种情况下,perl不是把参数表都送给shell去解释,而是把一个参数视为需执行的命令,把其余参数不加解释地作为参数传给命令。
System “grep’fred flintstone’buffaloes”; #shell
System “grep”,”fred flintstone”,”buffaloes”;#使用列表
%ENV(大写)的数组提供一种检查、修改当前环境的方法。
 
二、使用单引号
将一个/bin/sh shell命令置于两个单引号之间。和shell一样,这种方法可以引发一个命令的执行,并且等待其执行结束,它在执行时使用标准输出。和shell不同的是,结果文本不是在所在位置展开作为perl的一个输入结构而是变成两个单引号之间的字符串的值。
$ now=”the time is now”.’date’
如这个命令用于数组环境,输出的是字符串列表。
位于单引号内的命令的标准输入和标准错误是从PERL进程继承过来的,这就说明可以将命令的标准输出作为单引号间字符串的值来使用。如把标准错误合并到标准输出中去,这样命令就能同时取得标准错误和标准输出,可以这样使用shell的2>&1结构:die”rm spoke!”if’rm frmd 2>&1’;
三、把进程用作文件句柄
坚线位于命令的右侧,表示文件句柄对读操作开放,即获取命令的标准输出。
Open(WHOPROC,”who”|);
@whosaid=;
左边放置竖线来为写打开一个进程文件句柄,例如:
Open(LPR,“|lpr-Pslatewriter”);
Print LPR @rockerport;
Close(LPR);
Open(LPR,”|LPr- Pslatewriter>/dev/null 2>&1”);
其中 >/dev/null 使标准输出被重定向到空设备。 2>&1 使标准输出被送到哪儿就把标准错误送到哪儿,同时还将结果中的错误信息舍去。
四、使用fork
创建当明进程的拷贝,这份拷贝可共享同一段可执行代码、变量,甚至已打开的文件。fork操作符的返回值:子进程的fork操作符返回0值,而父进程的返回非0值。
 Exec操作符和system操作符类似,但它不会引发新进程来执行shell命令,而是用shell取代当前进程。在成功执行exec操作符后perl程序结束,且由被调用的程序取代。
System(“date”);
Unless(fork){#Fork返回0,因此为子进程并且执行;
 Exec(“date”);#子进程变成date命令
}
Wait;# 父进程等待子程(date)完成,即父子进程同时运行,但输出分先后。
操作
标准输入
标准输出
标准错误
是否等待
System( )
由程序中继承
由程序中继承
由程序中继承
Backquoted 串
由程序中继承
作为串值捕获
由程序中继承
文件句柄输出时的open()命令
连接到文件句柄
作为串值捕获
由程序中继承
只在close( )时等待
文件句柄输入时的open()命令
由程序中继承
连接到文件句柄
由程序中继承
只在close( )时等待
Fork,exec,wait
用户选定
用户选定
用户选定
用户选定
发送和接收信号?
15 其它数据转换
查找子串:定位子字符串第一次在串中出现的位置,并且可以以整数形式返回其第一个字符的位置。$where=index(“hello”,”e”); 1
如果子串在串中多个位置出现,index()操作符返回最左边的位置。若要查找其它位置则需要传给index()第三个参数,这个参数代表index()返回的最小值。
$where=index(“hello world”,”l”);返回2(第一个 l)
$where=index(“hello world”,”l”,0);返回2(第一个 l)
$where=index(“hello world”,”l”,1);返回2(第一个 l)
$where=index(“hello world”,”l”,3);返回2(第一个 l)
$where=index(“hello world”,”o”,5);返回7(第二个o)
$where=index(“hello world”,”o”,8);返回-1(8后面就没了)
Rindex( ) 从右边开始寻找位于最右边的子串,但返回值仍和以前一样,是字符串最左边位置的字符数。当使用第三个参数时,返回值可能小于等于指定位置的值。
提取和替换子串
$s=substr($string,$start,$length)
子串长度等于或小于0时返回空串。子串长度大于0 时,如果起始位置小于0,则起始位置应从串尾往左计算。
如果起始位置位于字符串首之前(即负责的绝对值大于字符串长度),则把符串首作为起始位置。如果起始位置超过字符串尾,则返回空串。
省略长度参数相当于此参数取最大值,即提取从指定位置的字符串尾的所有字符。
使用sprintf( )格式化数据
由于 sprintf printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。这也导致 sprintf printf 有用得多。
1.       格式化数字字符串
sprintf 最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf在大多数场合可以替代itoa。如:
// 把整数 123 打印成一个字符串保存在 s 中。
sprintf(s, "%d", 123);   // 产生 "123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); // 产生: "    123    4567"
当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); // 产生: "123         4567"
也可以按照16进制打印:
sprintf(s, "%8x", 4567); // 小写 16 进制,宽度占 8 个位置,右对齐
sprintf(s, "%-8X", 4568); // 大写 16 进制,宽度占 8 个位置,左对齐
这样,一个整数的16进制字符串就很容易得到,但我们在打印16进制内容时,通常想要一种左边补0的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0就可以了。
sprintf(s, "%08X", 4567); // 产生: "000011D7"
上面以”%d”进行的10进制打印同样也可以使用这种左边补0的方式。
这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1的内存16进制表示形式,在Win32平台上,一个short型占2个字节,所以我们自然希望用4个16进制数字来打印它:
short si = -1;
sprintf(s, "%04X", si);
产生“FFFFFFFF”,怎么回事?因为spritnf是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈时被压进来的到底是个4字节的整数还是个2字节的短整数,所以采取了统一4字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32位的整数-1,打印时4个位置不够了,就把32位整数-1的8位16进制都打印出来了。如果你想看si的本来面目,那么就应该让编译器做0扩展而不是符号扩展(扩展时二进制左边补0而不是补符号位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, "%04X", si);
sprintf 和printf还可以按8进制打印整数字符串,使用”%o”。注意8进制和16进制都不会打印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16进制或8进制表示。
2.       控制浮点数打印格式
浮点数的打印和格式控制是sprintf的又一大常用功能,浮点数使用格式符”%f”控制,默认保留小数点后6位数字,比如:
sprintf(s, "%f", 3.1415926);    // 产生 "3.141593"
但有时我们希望自己控制打印的宽度和小数位数,这时就应该使用:”%m.nf”格式,其中m表示打印的宽度,n表示小数点后的位数。比如:
sprintf(s, "%10.3f", 3.1415626);   // 产生: "     3.142"
sprintf(s, "%-10.3f", 3.1415626); // 产生: "3.142     "
sprintf(s, "%.3f", 3.1415626); // 不指定总宽度,产生: "3.142"
注意一个问题,你猜
int i = 100;
sprintf(s, "%.2f", i);
会打出什么东东来?“100.00”?对吗?自己试试就知道了,同时也试试下面这个:
sprintf(s, "%.2f", (double)i);
第一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个”%f”。而函数执行时函数本身则并不知道当年被压入栈里的是个整数,于是可怜的保存整数i的那4个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。
不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编排的结果是否正确。J
字符/Ascii码对照
我们知道,在C/C++语言中,char也是一种普通的scalable类型,除了字长之外,它与short,int,long这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年该把这个类型叫做“byte”,然后现在就可以根据实际情况,使用byte或short来把char通过typedef定义出来,这样更合适些)
于是,使用”%d”或者”%x”打印一个字符,便能得出它的10进制或16进制的ASCII码;反过来,使用”%c”打印一个整数,便可以看到它所对应的ASCII字符。以下程序段把所有可见字符的ASCII码对照表打印到屏幕上(这里采用printf,注意”#”与”%X”合用时自动为16进制数增加”0X”前缀):
for(int i = 32; i < 127; i++) {
    printf("[ %c ]: %3d 0x%#04X/n", i, i, i);
}
3.       连接字符串
sprintf 的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,从而在许多场合可以替代strcat,但sprintf能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); // 产生: "I love CSDN. "
strcat 只能连接字符串(一段以’/0’结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以’/0’结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的’/0’来结尾。如果直接连接,不管是sprintf还是strcat肯定会导致非法内存操作,而strncat也至少要求第一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。比如:
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:
sprintf(s, "%s%s", a1, a2); //Don't do that!
十有八九要出问题了。是否可以改成:
sprintf(s, "%7s%7s", a1, a2);
也没好到哪儿去,正确的应该是:
sprintf(s, "%.7s%.7s", a1, a2);// 产生: "ABCDEFGHIJKLMN"
这可以类比打印浮点数的”%m.nf”,在”%m.ns”中,m表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m没什么大用,还是点号后面的n用的多。自然,也可以前后都只取部分字符:
sprintf(s, "%.6s%.5s", a1, a2);// 产生: "ABCDEFHIJKL"
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在sprintf的实现中也被考虑到了,sprintf采用”*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子可以变成:
sprintf(s, "%.*s%.*s", 7, a1, 7, a2);
或者:
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
sprintf(s, "%-*d", 4, 'A'); // 产生 "65 "
sprintf(s, "%#0*X", 8, 128);    // 产生 "0X000080" "#" 产生 0X
sprintf(s, "%*.*f", 10, 2, 3.1415926); // 产生 "      3.14"
4.       打印地址信息
有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32位的数,你完全可以使用打印无符号整数的”%u”把他们打印出来:
sprintf(s, "%u", &i);
不过通常人们还是喜欢使用16进制而不是10进制来显示一个地址:
sprintf(s, "%08X", &i);
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的”%p”:
sprintf(s, "%p", &i);
我觉得它实际上就相当于:
sprintf(s, "%0*x", 2 * sizeof(void *), &i);
5.       利用sprintf的返回值
较少有人注意printf/sprintf函数的返回值,但有时它却是有用的,spritnf返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf调用结束以后,你无须再调用一次strlen便已经知道了结果字符串的长度。如:
int len = sprintf(s, "%d", i);
对于正整数来说,len便等于整数i的10进制位数。
下面的是个完整的例子,产生10个[0, 100)之间的随机数,并将他们打印到一个字符数组s中,以逗号分隔开。
#include
#include
#include
int main() {
    srand(time(0));
    char s[64];
    int offset = 0;
    for(int i = 0; i < 10; i++) {
       offset += sprintf(s + offset, "%d,", rand() % 100);
    }
    s[offset - 1] = '/n';// 将最后一个逗号换成换行符。
    printf(s);
    return 0;
}
设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的strcat效率高,因为strcat每次调用都需要先找到最后的那个’/0’的位置,而在上面给出的例子中,我们每次都利用sprintf返回值把这个位置直接记下来了。
6.       使用sprintf的常见问题
sprintf 是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。
Ø          缓冲区溢出
第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用”%.ns”的形式指定最大字符数。
Ø          忘记了第一个参数
低级得不能再低级问题,用printf用得太惯了。//偶就常犯。:。(
Ø          变参对应出问题
通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应”*”的那些参数,都提供了吗?不要把一个整数对应一个”%s”,编译器会觉得你欺她太甚了(编译器是obj和exe的妈妈,应该是个女的,:P)。
7.       strftime
sprintf 还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为了在出现问题时可以推卸责任吧。这里举个例子:
time_t t = time(0);
// 产生 "YYYY-MM-DD hh:mm:ss" 格式的字符串。
char s[32];
strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t));
sprintf 在MFC中也能找到他的知音:CString::Format,strftime在MFC中自然也有她的同道:CTime::Format,这一对由于从面向对象哪里得到了赞助,用以写出的代码更觉优雅。
 
SORT 的高级排序
Sort 程序 数据列表
数值比较:$a<=>$b (返回值:1表示>; 0表示=;-1表示<;)
字符比较:$a cmp $b (返回值:1表示 >; 0表示=;-1表示<;)
$a<=>$b ||$a cmp $b
 
拼写替换
tr/ 旧字符串 / 新字符串 /
如果新字符比旧字符串短,那么就重复新串的最后一个字符直到两个字符串等长。
如果新字符串是空串且不使用选项则认为新字符串与旧字符串相同。可用于计算同一个字符在字符串中的数量。
tr/ 旧字符串 / 新字符串 /d
其尾部加d,表示删除。即那些旧字符串中出现但又不在新字符串中出现的字符就被直接从字符串中删除。
$_=”fred and barney”;
$count=tr/a-z/ABCDE/d;# $_现在是”ED AD BAE”
tr/ 旧字符串 / 新字符串 /c
那就意味着可用全部256个字符补充旧字符串。旧字符串中的所有字符会被从可用 字符集中删除;剩下的字符由高到低排列,组成新的旧字符串。
 
$_=”fred and barney”;
$count=tr/a-z/ /c;# $_没有变,而 $count是2;(旧串为两空格,新串默认与旧串相同。)
$count=tr/a-z/_ /c;# $_” fred_and_barney”
$count=tr/a-z/ /cd;# $_ ” fredandbarney”(把在旧串出现,但在新串中没有出现的空格删除。)
tr/ 旧字符串 / 新字符串 /s
可以把转换结果中的多个重复部分压缩成一个。
$_=”aaabbbcccdefghi”; tr/defghi/abcddd/s;#$_ 现在是 ”aaabbbcccabcd”

你可能感兴趣的:(perl)