#!/usr/bin/perl
# 启用严格模式
use strict;
# 启用内建的警告功能
use warnings FATAL => 'all';
##### 其它 #####
# 老地方变量 $_,自动根据上下文推导此变量指向谁
foreach (qw/Alex Meng Cai/){
print $_; # 自动打印当前元素
}
$_ = "Hello\n";
print; # 自动打印老地方变量
# 读取一行标准输入
my $line = <STDIN>;
# 在不产生歧义的前提下,函数调用的括号是可选的
chomp $line; # 去除尾部的换行符
##### 标量数据类型 #####
# 数字,在内部总是以双精度浮点数来表示
my $num = 94605; # my关键字创建词法变量,其作用域为当前代码块
$num = 6.02;
$num = 6.02e23;
# 可以使用下划线作为千分隔符
$num = 94_605;
# 非十进制整数
$num = 0b101010;
$num = 0777;
$num = 0xFF;
# 数字支持的操作符包括 + - * / %
$num = 10 % 3;
# 字符串
# 单引号字符串,总是字面含义
my $str = 'Hello \n World';
print $str; # Hello \n World
# 双引号字符串,支持转义字符
my $name = 'Alex';
my multiline = "
支持多行字符串
";
# 支持变量内插
print "Hello $name ${name} \n"; # Hello Alex!
# 字符串支持的操作符:
# 字符串连接
my $hello = 'Hello ' . $name;
# 字符串重复,重复次数非整数则向下取整,重复次数为0则生成空串
my $hello3 = $hello x 3; # HelloHelloHello
# 字符串和数字的自动转换
print 5 . 5; # 只要是.操作符,就都认为是字符串,输出55
print 5 * "5"; # 只要是算术操作符,就认为是整数,输出25
# 甚至是 "5哈哈" * 5 也可以得出25
##### 标量变量 #####
# 标量变量必须以 $ 开头
# 支持双目赋值操作符
$num += 1;
$num *= 1;
# 未经明确初始化的变量,其初值未undef,可以转换为数字0
while ($n < 10) {
print($n++) # 12345678910
}
# 下面的函数用于判断一个变量是否被初始化
$isdefined = defined($n);
##### 控制结构 #####
# 裸块,可以用于控制变量作用域
{
my i = 0;
}
# if分支
# 对于字符串来说,''和'0'的值为假
# 对于数字来说, 0的值为假
# 其它类型,先转换为数字或字符串再判断
# 支持取反操作符 !
if ($name eq 'Alex') {
# 字符串比较操作符 eq ne lt gt le ge
}
elsif ($num == 0) {
# 数字比较操作符 == != < > <= >=
}
else {}
# 后缀式条件修饰词
print "$n is zero" if $n == 0;
# unlese结构,当表达式不满足的时候执行
unless (0 == 1) {
print "Of course!";
}
else {}
# until循环,直到表达式不满足时才退出循环
until (0 == 1) {
print "天荒地老";
}
# while循环
my $count = 0;
while ($count < 10) {
$count++;
}
# foreach循环
foreach $user (qw/Alex Meng Cai/){
print $user;
}
# for循环
# 使用嵌套循环时,可以用标签
LABEL: for (my $i = 0; $i < 10; $i++) {
# 下面三个关键字,后面的标签都是可选的
last LABEL; # 中止循环,类似于其它语言的break
next LABEL; # 进行下一轮迭代,类似于其它语言的continue
redo LABEL; # 重新进行本轮迭代
}
##### 数组 #####
# 列表直接量,使用()包围
# 数组变量,使用@作为前导
@empty = ();
@nums = (1, 2, 3);
@nums = (1 .. 100); # 100个数字的列表
# $#nums表示获取数组nums的最后一个元素的索引值,即长度-1
$num_count = $#nums + 1;
# 字符串的列表
@users = ('Alex', 'Meng', 'Cai');
@users = qw(Alex Meng Cai); # 等价于上面,空格分隔元素
# qw简写可以使用任何界定符
qw{};
# 列表式赋值:
($alex, $meng) = ('Alex', 'Meng');
# 互换两个元素的值
# 注意引用数组元素时需要标量前缀$
($nums[ 0 ], $nums[ 1 ]) = ($nums[ 1 ], $nums[ 0 ]);
# 将数组作为栈来操作
$num = pop @nums;
push @nums, 0;
# pop/push是在数组的尾部操作,shift/unshift则是在头部
@nums = (0, 1, 2);
shift @nums;
unshift @nums,0;
unshift @nums,-1;
print @nums; # -1012
# 数组在字符串中的内插,元素之间自动加空格
print "\n@nums"; # -1 0 1 2
# 反转操作符,获得列表的逆序
@nums_r = reverse @nums;
# 排序操作符,以ASCII序排列
sort @nums_r;
##### 哈希 #####
%users = ('Alex', 30, 'Meng', 27);
# 胖箭头语法:
%users = (
'Alex' => 30,
'Meng' => 27
);
#CRUD操作
$users{'Alex'} = 32;
$users{'Meng'} = 28;
$users{'Cai'} = 3;
print $users{'Ya'}; # 获取不存在的键会得到undef
exists $users{'Ya'}; # 判断指定的键是否存在
delete $users{'Ya'}; # 删除指定的键
# 哈希可以和列表相互转换,转换为列表是,键值都作为列表元素
# 但是转换后,键值对的顺序不能保证
@users = %users;
print scalar @users; # 6
# 获得键列表和值列表
my @keys = keys %users;
my @vals = values %users;
# each函数,每次调用,返回下一个键值对(的列表)类似于其它语言中的迭代器
while (($k, $v) = each %users) {
printf "%s = %d\n", $k, $v;
}
# %ENV存放环境变量
printf "$ENV{PATH}\n"
##### 子程序 #####
use feature 'state';
sub add {
# 参数存放在名为@_的数组中,注意$_[0]和老地方变量$_毫无关系
$_[0] + $_[1];
# 最后一个表达式的结果,作为子程序的返回值,你也可以使用 return 语句随时返回
}
# 调用子程序,使用&表达式
$result = &add(1, 2);
print $result; # 3
# 对于已经知晓子程序定义的地方,可以省略&
print add(1, 2); # 3
# 如果子程序和Perl内置函数同名,不得省略&
sub chomp {print('chomp')};&chomp();
sub counter {
# 持久性局部变量,跨越多次子程序存在,但是作用域限定在子程序内部
state $count = 0;
$count++;
print $count;
}
counter counter counter; # 123
# 引入模块以使用 # 在编译阶段,遇到下面的语句后会自动查找相关的代码并加载到当前脚本中 # /usr/share/perl/5.18/File/Basename.pm use File::Basename; # 你可以仅仅导入模块中特定的函数 use File::Basename qw/basename/; my $fname = '/usr/bin/perl'; # 调用导入的函数 print basename $fname; # perl # 不导入任何函数 use File::Basename qw//; # 但是你仍然可以用全限定名称调用之 File::Basename::basename($fname)
在Perl中,相同的表达式,出现不同的地方(上下文)时,具有完全不同的意义。上下文包括标量上下文、列表上下文,在标量上下文中,Perl表达式的期望结果是标量,否则是列表。例如下面这段代码:
### 产生列表的表达式 ### # 列表上下文 @users = reverse qw/Alex Meng Cai/; print "@users\n"; # Cai Meng Alex # 标量上下文 $users = reverse qw/Alex Meng Cai/; print "$users\n"; # iaCgneMxelA ### 产生标量的表达式 ### @nums = 6*5; # 获得单元素的列表,元素值30 @nums = undef; # 获得单元素的列表,元素值undef @nums = (); # 这样才能获得空列表
表达式@users在列表上下文中,会产生数组元素的列表,在标量上下文中则返回元素个数:
@users = reverse qw/Alex Meng Cai/; print "@users\n"; # Cai Meng Alex print 0 + @users; # 3 $n = @users; # n == 3
某个具体表达式在不同上下文中的返回值是什么,各有不同,例如:
| 表达式 | 标量上下文 | 列表上下文 |
| reverse | 先把列表元素连接在一起,然后得到一个逆序的字符串 | 逆序后的列表 |
| sort | undef | 排序后的列表 |
| @ | 数组元素个数 | 数组对应的列表 |
某些情况下,是列表上下文还是标量上下文,不是很显眼,例如下面都是列表上下文:
( $alex, $meng ) = something; ( $alex ) = something;
函数scalar可以强制切换到标量上下文:
@nums = (1, 2, 3); print @nums; # 123 print scalar @nums; # 3
<STDIN>在列表上下文中,读取文件中所有行,存入列表,直到EOF(在控制台可以Ctrl + D触发EOF):
@lines = <STDIN>;
$url = 'http://blog.gmem.cc/perl-faq?q=正则式';
$_ = $url;
# 对老地方变量进行匹配
if (/^https?:\/\/.*$/) {
print "Valid URL";
}
# /***/实际上是m/***/的简写为了避免对 / 转义,可以使用m{***}
# i 表示大小写无关的匹配
if (m{^HTTPS?://.*$}i) {
print "Valid URL";
}
# s 可以让 . 匹配任何字符,包括换行符
# x 可以在模式中添加任何空白,以方便阅读
# m 表示多行模式,^$可以匹配字符串内的换行符
m{ ^HTTPS?://.*$ }xis;
# 绑定操作符 string =~ pattern。不使用绑定操作符时,针对老地方变量进行匹配
$_ =~ m{ ^HTTPS?://.*$ }x;
# 模式中可以内插变量
m{ ^($scheme)?://.*$ };
# 捕获
if (m{ ^(https?)://(.*)\?(.*)}ix){
# 在作用域内部使用$1 ... 引用捕获到的分组
printf "Scheme: $1, Host: $2, Query: $3";
# Host: blog.gmem.cc/perl-faq, Query: q=正则式
}
# 捕获,使用命名分组
if (m{ ^(?<scheme>https?)://(?<host>.*)\?(?<query>.*)}ix){
# 在作用域内部使用$+{name} 引用捕获到的分组
printf "Scheme: $+{scheme}, Host: $+{host}, Query: $+{query}";
}
# 自动特殊捕获
$&; # 匹配到的子串
$`; # 匹配子串前面的部分
$'; # 匹配子串后面的部分
# 在列表上下文中 m/***/ 返回所有捕获到的子串的列表
($first, $second, $third) = 'ONE TWO THREE' =~ /(\S+) (\S+) (\S+)/;
print $first; # ONE
使用Perl正则式替换,可以直接修改目标字符串的内容:
$str = "Hello Alex Hello Meng";
$_ = $str;
# s/// 用于替换,可将/换成其它符号例如#
# 标记g表示全局替换,ixs也可以使用
s/ (?<=Hello\s)\w+ /World/xig;
print "$_\n"; # Hello World Hello World
# s/// 的返回值为布尔型,如果替换成功则返回真
# 替换的时候也可以使用 =~ 操作符
# 替换的时候也支持使用捕获
# \U$1表示把捕获到的第一个分组转换为大写
# \L$1表示转换为小写
if($str =~ s/ (?<=Hello\s)(\w+) /\U$1/xig ){
print "$str\n"; # Hello ALEX Hello MENG
}
# 基于正则式分割字符串 @fields = split /separator/, $str; # 拼接字符串 $str = join $glue, @fields;
# 读取标准输入
$line = <STDIN>;
chomp $line;
# 循环读取标准输入
while (<STDIN>) {
print "You entered: $_";
$|++; # 刷出
}
# @ARGV为程序参数数组,程序启动时自动创建,但你随时可以修改它
@ARGV = qw{/home/alex/Lua/projects/openrestry/logs/error.log};
# 从程序参数指定的文件(可以指定多个文件)中逐行读取
while (<>) { # 钻石操作符
# 下面的两个函数调用没有指定参数,自动使用$_
chomp;
print;
}
# 写出标准输出
# 打印程序参数指定的文件
print <>;
# 支持C风格的Printf
# %s 字符串
# %g 自适应的数字输出
# %d 十进制整数输出
# %f 浮点数输出
# %10s 输出为字符串,长度10字符,右对齐左补空格
# %12.4f 输出为浮点数,保留4位小数
printf "%s %g %d", 'string', 6.12, 94605;
# 从 5.10开始,支持Perl 6的say函数,该函数类似于print,但是会自动在尾部添加\n
use 5.010;
say "Hello!";
文件句柄(Filehandle)代表Perl程序和外界之间的IO联系,它是一个名字。
预定义的文件句柄包括:
| 句柄 | 说明 |
| STDIN | 标准输入流 |
| STDOUT | 标准输出流 |
| STDERR | 标准错误流 |
| DATA | |
| ARGV | |
| ARGVOUT |
你可以创建并使用自己的文件句柄:
# < 表示仅仅用于输入(读取)
open CONFIG, '</etc/mysql/my.cnf';
# > 表示仅仅用于输出(写入)
open PIDFILE, '>/var/run/mysql.pid';
# >> 表示用于追加输出
open LOG, '>>/var/log/mysql.log';
# 这样写也可以
my $success = open LOG, '>>', '/var/log/mysql.log';
# 如果打开成功,则返回值为真
unless ($success) {
# die用于终结程序,并返回非0 退出码
# $!存放上一次函数调用的错误信息
die "Failed to open file : $!";
# warn可以输出警告信息
}
# 使用文件句柄
# 读取
while (<CONFIG>) {
# 逐行读取
print;
}
# 写入
print CONFIG "...";
# 改变默认文件句柄
select CONFIG;
# 这样,默认输出到CONFIG
print '...';
# 设置变量$|的值为1,则当前默认句柄的缓冲区立即flush
$| = 1;
# 不再使用时,你需要关闭文件句柄
close LOG;
使用文件操作符,示例:
if(-e $filename){
print("File $filename exists\n");
}else{
print("File $filename does not exists\n");
}
支持的文件操作符如下,所谓操作符都可以前缀!来取反:
| 操作符 | 说明 |
| -r | 测试文件是否可被Effective UID/GID读 |
| -w | 测试文件是否可被Effective UID/GID读 |
| -x | 测试文件是否可被Effective UID/GID执行 |
| -o | 测试文件的所有者是否为Effective UID |
| -R |
类似上面四个 但是针对Real UID/GID |
| -W | |
| -X | |
| -O | |
| -e | 测试文件是否存在 |
| -z | 测试文件是否为空(0字节) |
| -s | 测试文件是否非空 |
| -f | 是否为普通文件 |
| -d | 是否为目录 |
| -l | 是否为符号链接 |
| -p | 是否为命名管道 |
| -S | 是否为套接字 |
| -b | 是否为块特殊文件 |
| -c | 是否为字符特殊文件 |
| -t | 是否被TTY打开的文件句柄 |
| -u | 文件模式的setuid位是否设置 |
| -g | 文件模式的setgid位是否设置 |
| -k | 文件模式的sticky为是否设置 |
| -T | 是否为ASCII或UTF-8文本文件 |
| -B | 是否为二进制文件 |
# 获取目录构成的数组,支持通配符
my @dirs = </usr/local/tomcat_refactoring_*>;
foreach my $dir (@dirs) {
# 可以调用Shell命令
system "rm -rf $dir/logs";
# 下面的函数用于创建新目录,由于umask的原因,默认只能创建0755
my $omask = umask;
# 设置umask,即默认权限
umask 0;
mkdir "$dir/logs", 0777;
umask $oumask;
# 修改文件模式
chmod "$dir/logs", 777
# 移除文件,可以指定多个文件
# 除非指定perl -U,并且当前用户为超级用户,否则下面的函数不会删除目录
unlink $file1, $file2
}
# 方式一,使用system函数,调用完毕后返回当前脚本
system("sh", "script.sh", "--arg" );
system("sh script.sh --arg");
# 方式二,使用exe函数,调用永远不会返回
exec '/bin/echo', 'Your arguments are: ', @ARGV;
exec "sort $outfile | uniq";
# 方式三,反引号操作符
`script.sh --arg`
# 等价的qx//
qx/script.sh --option/;
$ENV{'LUA_PATH'} = '';
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
$path = $ENV{'PATH'};
安装Intellij插件Perl,然后安装下面的模块,以支持调试:
cpanm -i Devel::Camelcadedb
wget http://www.cpan.org/src/5.0/perl-5.26.1.tar.gz tar xzf perl-5.26.1.tar.gz && rm perl-5.26.1.tar.gz cd perl-5.26.1 ./Configure -Dotherlibdirs=/home/alex/Perl/5.26/lib/perl5/site_perl/5.26.1 -Dprefix=/home/alex/Perl/5.26 -d make -j8 && make install cd /home/alex/Perl mv perl-5.26.1 /home/alex/Perl/5.26/src cd 5.26/bin curl -L https://cpanmin.us/ -o cpanm chmod +x cpanm
字面意思是综合性Perl归档网络(Comprehensive Perl Archive Network),它是一个Perl模块的仓库。
有时候,也指cpan 命令,它是由CPAN提供的,自1997年开始随Perl发布的包管理工具。
一个脚本工具,用于从CPAN下载、解包、构建、安装Perl模块。
常用子命令:
| 子命令 | 说明 |
| -i, --install | 默认子命令,安装Perl模块 |
| -U,--uninstall | 删除Perl模块 |
| --self-upgrade | 升级cpanm,等价于cpanm App::cpanminus |
| --info | 以AUTHOR/Dist-Name-ver.tar.gz 格式显示发行版的信息 |
| --installdeps | 安装目标模块的依赖 |
| --look | 下载、解包,然后通过Shell打开目录 |
常用选项,默认选项可以通过环境变量PERL_CPANM_OPT 指定:
| 选项 | 说明 |
| -f, --force | 强制安装模块,即使测试失败 |
| -n, --notest | 跳过模块测试 |
| --test-only | 仅运行测试,不安装 |
| -S, --sudo | 切换到root进行安装 |
| -v, --verbose | 冗长模式,自动开启 --interactive |
| -q, --quiet | 安静模式,比默认情况更少的输出 |
| -l, --local-lib | 设置一个安装路径前缀,此前缀必须和local::lib兼容,例如:
cpanm -i -l /usr Module::Load |
| -L, --local-lib-contained | 与-l类似,但是同时开启 --self-contained |
| --self-contained | 检查依赖时,假设任何非核心模块都没有在系统上安装,如果你希望把应用程序的依赖一起分发到其它机器上,可以使用 |
| --interactive | 启用交互式的配置(例如Makefile.PL) |
@INC 这个特殊的数组,包含了可以从中寻找并加载Perl模块的一系列目录。该数组的值和一系列因素相关。
@INC的默认值在Perl解释器被编译的时候确定,你可以使用命令获得此默认值:
env -i perl -V # 输出片段: # Summary of my perl5 (revision 5 version 16 subversion 3) configuration: # ... # Built under linux # Compiled at Nov 20 2015 03:29:53 # 下面的内容就是@INC的默认值 # @INC: # /usr/local/lib64/perl5 # /usr/local/share/perl5 # /usr/lib64/perl5/vendor_perl # /usr/share/perl5/vendor_perl # /usr/lib64/perl5 # /usr/share/perl5 # .
注意结尾的点号,表示当前工作目录。当以-T 运行Perl解释器时该目录被忽略。
要改变此默认值,需要在编译时配置
./Configure -Dotherlibdirs=/usr/lib/perl5/site_perl/5.16.3
环境变量PERL5LIB 或者PERLLIB 会影响@INC的取值。如果PERL5LIB没有定义,才自动使用PERLLIB。
通过PERL5LIB你可以指定一系列分号分隔的目录,这些目录会自动添加到默认值的前面(优先搜索)。
使用-I 选项,可以附加一系列分号分隔的目录到@INC的头部:
perl -I ~/perl5
你可以通过类似下面的代码,继续附加一系列分号分隔的目录到@INC的头部:
use lib ("/dir1", "/dir2");
你可以直接修改@INC,就像普通Perl数组一样。
Leave a Reply