Perl Cookbook

Table of Contents

1 Perl 环境

1.1 Module::CoreList

列出本地所有模块,以及是否是核心模块:

# filename: corelist.pl

use Module::CoreList;

if ($ARGV[0]) {
    foreach (Module::CoreList::find_modules(qr/$ARGV[0]/)) {
        printf "%s $_\n", Module::CoreList::is_core($_);
    }
}

1.2 REPL

最简单的,使用 Perl 的调试器:

$ perl -de1

参数 -e 后面可以跟任意表达式。

Perl 调试器默认使用的 Term::ReadLine 来处理交互式,Term::ReadLine 不支持 shell 的快捷键,如需要快捷键支持就安装 Term::ReadLine::Gnu。

2 数据格式

2.1 JSON

use JSON;

my %hash = ("a" => 1,
            "b" => 2);

print to_json(\%hash);

my $str = '{"msg": "hello world"}';
my $hash_ref = from_json($str);

3 正则表达式

Regexp::Common 模块中有非常多比较常见的正则表达式可以直接拿来使用,比如验证 IP 地址的:

use Regexp::Common;
print "yes" if "127.0.0.1" =~ /^$RE{net}{IPv4}$/;

4 线程

多线程示例:获得域名列表中每个域名的 whois 数据:

use v5.10;
use threads;
use Thread::Queue;
use Domain::PublicSuffix;

use constant THREAD_POOL_SIZE => 100;
my $thread_total = 100;
my $suffix = Domain::PublicSuffix->new();
my $queue = Thread::Queue->new();
my @threads;

open F, "<", "all_host";

foreach my $line (<F>) {
  chomp($line);
  $queue->enqueue($line);
}
$queue->end();
close F;

for(1..THREAD_POOL_SIZE) {
  push @threads, threads->create(sub {
    while (defined(my $domain = $queue->dequeue)) {
      $root = $suffix->get_root_domain($domain);
      my $whois_info = `whois $root`;
      say $whois_info;
    }
  })
}

$_->join for @threads;

5 HTTP

5.1 Web请求

GET 请求:

use LWP::UserAgent;

my $ua = LWP::UserAgent->new;
my $resp = $ua->get("http://www.shellcodes.org");

if ($resp->is_success) {
    print $resp->content;
} else {
    print 'error';
}

POST 请求:

use LWP::UserAgent;

my $ua = LWP::UserAgent->new;
my $resp = $ua->post("http://host/login.php", [
                         "user" => "admin",
                         "password" => "admin",
                     ]);

if ($resp->is_success) {
    print $resp->content;
} else {
    print 'error';
}

5.2 TLD

perl -MDomain::PublicSuffix -nle 'BEGIN{$s = Domain::PublicSuffix->new({'datafile' => "effectivetldnames.dat"})};chomp($_);print $s->getrootdomain($_)'

6 调试 Perl

进入调试器:perl -d [perlfile]

更多调试器用法请用 h 命令,或参考 perldebug 文档:

$ perldoc perldebug

6.1 Data::Dump

可以把复杂的数据结构打印出拉:

use Data::Dumper;

my %hash = ("a" => 1,
            "b" => 2);

print Dumper(\%hash);

输出:

$VAR1 = {
          'b' => 2,
          'a' => 1
        };

Data::Dump 模块的 dump 子程序打印出来的要更美观点:

use Data::Dump qw(dump);

my %hash = ("a" => 1,
            "b" => 2);

print dump(\%hash);

输出:

{ a => 1, b => 2 }

6.2 Dumpvalue

use strict;
use Dumpvalue;

my $dump = Dumpvalue->new();
my $hash = {
            name => 'lu4nx',
            blog => 'www.shellcodes.org'
           };

$dump->dumpValue(\$hash);

运行输出:

-> HASH(0x23a7f48)
   'blog' => 'www.shellcodes.org'
   'name' => 'lu4nx'

6.3 B::Deparse

当遇到比较复杂的 Perl 代码时,可以用 B::Deparse 模块来查看 Perl 编译器解析后的样子,便于调试源码。

比如以下一段 JAPH 代码:

$_='987;s/^(\d+)/$1-1/e;$1?eval:print"Just another Perl hacker,"';eval;

保存为 japh.pl,调用 B::Deparse 模块:

$ perl -MO=Deparse -f japh.pl

输出如下:

$_ = '987;s/^(\\d+)/$1-1/e;$1?eval:print"Just another Perl hacker,"';
eval $_;
japh.pl syntax OK

7 unpack

显示字符串的 16 进制内容:

print unpack('H*', 'hello world') # => 68656c6c6f20776f726c64

8 输入输出

select:改变 print、write 的输出句柄:

use strict;

open my $f, '>', 'output';
select($f);
print 'hello world';

将打印的内容输出到文件“output”中。

9 BEGIN、CHECK、INIT 和 END 块

先看这段代码:

use 5.010;

say 'hello';

BEGIN {
  say 'in BEGIN block';
}

运行输出如下:

in BEGIN block
hello

借助 Deparse 模块:

$  perl -MO=Deparse -f test.pl
in BEGIN block
sub BEGIN {
    require 5.01;
}
no feature ':all';
use feature ':5.10';
say 'hello';
sub BEGIN {
    say 'in BEGIN block';
}
test.pl syntax OK

可以看到,use 5.010 实质上被转换成:

sub BEGIN {
    require 5.01;
}

Perl 在初始化时首先执行 BEGIN 块的代码。

在初始化完成后,会再执行 CHECK 块,修改下代码:

use 5.010;

say 'hello';

BEGIN {
  say 'in BEGIN block';
}

CHECK {
  say 'in CHECK block';
}

运行输出:

in BEGIN block
in CHECK block
hello

CHECK 块有点特殊,如果代码里出现多个 CHECK 块,Perl 首先执行最后一个:

use 5.010;

CHECK {
  say 'CHECK1';
}

CHECK {
  say 'CHECK2';
}

CHECK {
  say 'CHECK3';
}

运行输出:

CHECK3
CHECK2
CHECK1

INIT 块在所有 CHECK 块执行完后才执行:

use 5.01000;

say 'hello';

BEGIN {
  say 'in BEGIN block';
}

CHECK {
  say 'in CHECK block';
}

INIT {
  say 'in INIT block';
}

输出如下:

in BEGIN block
in CHECK block
in INIT block
hello

最终程序在退出前执行 END 块的内容:

use 5.01000;

say 'hello';

BEGIN {
  say 'in BEGIN block';
}

CHECK {
  say 'in CHECK block';
}

INIT {
  say 'in INIT block';
}

END {
  say 'bye';
}

输出:

in BEGIN block
in CHECK block
in INIT block
hello
bye

10 goto

goto 语句可改变程序的执行流程,可以跳转到指定的 LABEL 块或者函数中运行。

例:

my $sum = 0;
LOOP:do {
  if ($sum % 2 == 0) {
    $sum++;
    goto LOOP;
  }
  print $sum, "\n";
  $sum++;
} while($sum < 10);

上面代码中 LOOP 就是一个 LABEL 块,这个 LABEL 标签也可以靠表达式来生成,现在,我修改成通过函数调用返回字符串:

sub make_label {
  return 'LOOP';
}

my $sum = 0;
make_label:
  do {
  if ($sum % 2 == 0) {
    $sum++;
    goto make_label;
  }
  print $sum, "\n";
  $sum++;
} while($sum < 10);

goto 也可以改变函数内部执行流程,将当前执行的子程序替换成别的子程序:

sub hello {
  print "hello world\n";
}

sub test {
  goto &hello;
  print "end\n";
}

test

输出:hello world