本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

Perl チートシート — 簡潔なリファレンス

Perl 5.38 の構文、正規表現、リファレンス、よく使う組み込み関数を網羅したチートシート。日常の約 80% のケースをカバーします。

Pl

Perl Perl 5.38

perl 5 (interpreter) · マルチパラダイム(手続き型 · OO · 関数型) · 動的型付け

おすすめの学習パス

まず `perl hello.pl` の実行と `use strict`/`use warnings` を習得 → スカラ、配列、ハッシュ、制御フローをマスター → サブルーチンとコンテキストを深く理解 → リファレンスで複雑なデータ構造を構築 → 正規表現でテキスト処理 → `eval` によるエラー処理と `bless` による OO を理解 → 必要に応じてスレッド、ネットワーク、時間、CPAN モジュールを学習。FAQ セクションは後で落とし穴回避に再読するのに適しています。

1.Hello World とビルド環境

Perl スクリプトの実行、use strict/warnings、コマンドラインツール。

最小プログラム

hello.pl として保存し、perl hello.pl で実行します。use strict と use warnings は必須です。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
say "Hello, world!";
# 実行:
# perl hello.pl
# 1 行目の shebang がインタプリタを指す
# say は自動改行、print は改行しない

実行と解釈

perl はスクリプトを直接解釈実行します。-c は構文チェックのみ、-e は単一行コードを実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# スクリプトを直接実行:
# perl hello.pl
# 構文チェック(実行しない):
# perl -c hello.pl
# コマンドラインの 1 行コード:
# perl -e 'print "hi\n"'
# 警告を有効化:
# perl -w hello.pl
# ロードパスを表示:
# perl -V
# 行単位処理(-n/-p):
# perl -ne 'print if /error/' log.txt
# インタプリタの場所:
# which perl

shebang と引数

#!/usr/bin/perl でインタプリタを宣言します。コマンドライン引数は @ARGV に、$0 はスクリプト名です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
# コマンドライン引数は @ARGV に格納:
my ($name) = @ARGV;
say "你好, $name" if defined $name;
# 実行:
# perl greet.pl Rex
# 全引数を走査:
for my $arg (@ARGV) {
say "参数: $arg";
}
# $0 はスクリプト名:
say "脚本: $0";
# 引数の個数:
my $count = scalar @ARGV;

出力とフォーマット

print は改行せず、say は自動で改行します。printf はフォーマット、sprintf は文字列を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use warnings;
use feature 'say';
print "hello\n"; # 手動改行
say "world"; # 自動改行
# フォーマット出力:
printf "姓名: %s, 年龄: %d\n", "Rex", 5;
# 文字列を返す:
my $msg = sprintf("%d + %d = %d", 1, 2, 3);
say $msg;
# ハンドルへ出力:
print STDERR "出错啦\n";
# よく使う書式:
# %s 文字列 %d 整数 %f 浮動小数点 %x 16 進数

入力の読み込み

<STDIN> は 1 行(改行込み)を読み込みます。chomp で末尾改行を削除、EOF では undef を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use feature 'say';
# 1 行読む(改行含む):
my $line = <STDIN>;
chomp $line; # 末尾の改行を除去
say "输入: $line";
# 1 行の整数を読む:
my $num = <STDIN>;
chomp $num;
$num += 0; # 数値に変換
# EOF まで複数行を読む:
while (my $l = <STDIN>) {
chomp $l;
last if $l eq 'quit';
say "读到: $l";
}
# 入力がなければ <STDIN> は undef を返す

クォート演算子

q() はシングルクォート、qq() はダブルクォート、qw() は単語リスト、qx() はコマンド置換のセマンティクスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
# q() は挿入しない:
my $a = q(原样 $name);
# qq() は挿入できる:
my $b = qq(你好, $name);
# qw() は空白で単語に分割:
my @words = qw(alpha beta gamma);
# qx() はコマンドを実行して出力を取得:
my $pwd = qx(pwd);
chomp $pwd;
say "目录: $pwd";
# 利点:q 系演算子は任意のデリミタを使える:
# q{...} qq(...) qw!...!
# 引用符のエスケープを避けられる

use strict/warnings

use strict は変数宣言を強制し、ベアワードを禁止します。use warnings はランタイム警告を有効にします。モダン Perl の必須です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict; # スペルミス/裸の単語をエラーに
use warnings; # 実行時警告
# strict の三面:
# strict 'vars' 変数の宣言必須
# strict 'refs' シンボリックリファレンス禁止
# strict 'subs' 裸の関数名禁止
# 未宣言変数はコンパイルエラー:
# $foo = 1;
# エラー:Global symbol requires explicit package name
# 宣言すれば正常:
my $bar = 1;
say $bar;
# 本番コードでは常にこの 2 行を付ける

perldoc ドキュメント

perldoc は Perl 同梱で、関数、モジュール、組み込み変数、言語チュートリアルを調べられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 組み込み関数を調べる:
# perldoc -f map
# モジュールのドキュメントを調べる:
# perldoc JSON
# 初心者向けチュートリアル:
# perldoc perlintro
# 組み込み特殊変数:
# perldoc perlvar
# 演算子の優先順位:
# perldoc perlop
# 組み込み関数の全表:
# perldoc perlfunc
# モジュールのインストール場所:
# perldoc -l JSON
# 構文のクイックリファレンス:
# perldoc perlrequick

2.変数と定数

スカラ/配列/ハッシュの宣言、my/our/local/state、そして定数定義。

変数宣言

3 種類のシグナルが 3 種類のコンテナを表す。$ はスカラ、@ は配列、% はハッシュ。my はレキシカル変数を宣言する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
# 3 種類のシンボル:
my $scalar = 42;
my @array = (1, 2, 3);
my %hash = (name => 'Rex', age => 5);
# 宣言だけで代入しない:
my $later;
my (@a, %h);
# 一括代入:
my ($x, $y, $z) = (1, 2, 3);
# リストの分解:
my @nums = (1, 2, 3, 4);
my ($first, @rest) = @nums;
# $first=1, @rest=(2,3,4)

スカラ型

スカラは 1 つの値を保持します:数値、文字列、リファレンス。文字列と数値は自動で相互変換されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use warnings;
my $num = 3.14; # 数値
my $str = "hello"; # 文字列
my $bool = 1; # 偽値:0 '' undef '0'
my $undef; # 代入しないと undef
# 自動変換(コンテキストによる):
my $sum = "3" + 4; # 7(数値コンテキスト)
my $s = 3 . 4; # "34"(文字列連結)
# 文字列の先頭を数値に変換:
my $n = "12abc" + 0; # 12
# 長さ:
my $len = length $str; # 5

配列操作

配列は要素のリストです。push/pop は末尾、shift/unshift は先頭を操作し、添字は 0 から始まります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3);
$nums[0] = 10; # 要素の変更
say $nums[0];
# スタック操作:
push @nums, 4; # 末尾に追加
my $last = pop @nums; # 末尾をポップ
unshift @nums, 0; # 先頭に挿入
my $first = shift @nums; # 先頭をシフト
# 長さ:
my $count = scalar @nums;
# 最後の要素:
my $end = $nums[-1];
# スライス:
my @sub = @nums[0, 1];

ハッシュ操作

ハッシュはキーと値の対で、キーは文字列です。keys/values/each で反復し、exists/delete で管理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my %ages = (alice => 30, bob => 25);
# 読み書き:
say $ages{alice};
$ages{carol} = 28;
# 削除と判定:
delete $ages{bob};
exists $ages{alice}; # キーが存在するか
# 走査:
for my $name (keys %ages) {
say "$name: $ages{$name}";
}
# キーと値を同時に取得:
while (my ($k, $v) = each %ages) {
say "$k=$v";
}
# 注意:each はカーソルを消費、走査中に追加削除しない

定数の定義

use constant はコンパイル時定数を定義します。シグイルはなく、サブルーチンと同様に呼び出します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
# use constant で定数を定義:
use constant PI => 3.14159;
use constant MAX_RETRIES => 3;
# 定数はサブルーチンのように呼ぶ($ 記号なし):
my $area = PI * 2 * 2;
# 定数ハッシュ/配列:
use constant SETTINGS => { debug => 1 };
# 読み取り専用の意味:
# Readonly、Const::Fast モジュールが提供
# 定数は直接挿入できない:
# my $s = "PI = PI"; # 置換されない
# 挿入には連結か一時変数が必要

スコープ

my はブロック内のみのレキシカル変数、our はパッケージ(グローバル)変数、local は一時的にグローバルを上書き、state は静的なレキシカル変数です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'state';
# my はブロックスコープ:
{
my $temp = 10;
say $temp; # ブロック内で見える
}
# ブロック外に $temp は存在しない
# our はパッケージ変数(グローバル):
our $VERSION = '1.0';
# local は特殊変数を一時的に上書き:
{
local $/; # 区切り文字を一時的に undef に
my $all = <$fh>; # ファイル全体を一度に読む
}
# ブロックを出ると $/ は復元
# state は呼び出しをまたいで保持:
sub counter {
state $n = 0;
return ++$n;
}
say counter(); # 1
say counter(); # 2

デフォルト変数

$_ はデフォルト入力変数、@_ は引数リスト。よく使う特殊変数には $!(直近のシステムエラー)、$0(スクリプト名)などがあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# $_ はデフォルト変数:
for (1..3) {
say $_; # ループで $_ に代入される
}
# 多くの関数はデフォルトで $_ を操作:
for (@ARGV) {
chomp;
print;
}
# @_ は引数リスト:
sub sum {
my $t = 0;
$t += $_ for @_;
return $t;
}
# よく使う特殊変数:
# $0 スクリプト名 $! 直近のシステムエラー
# $/ 区切り文字 $| バッファリング解除
# $$ プロセスID $ARGV 現在のファイル名

文字列補間

ダブルクォート内では $scalar と @array が補間されます。シングルクォートはそのまま出力、波括弧は変数名の境界を区切ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @list = (1, 2);
# ダブルクォートで挿入:
my $a = "你好, $name"; # スカラー
my $b = "元素: @list"; # 配列(空白区切り)
# $ のエスケープ:
my $c = "转义: \$name";
# シングルクォートは挿入しない:
my $d = '你好, $name';
# 波括弧で変数名を区切る:
my $e = "${name}s house";
# インデックス/デリファレンス挿入:
my $h = [1, 2, 3];
my $f = "第二个: $h->[1]";
# エスケープ:\n 改行 \t タブ \\ バックスラッシュ

3.データ型

スカラ、undef、真偽値、リストとハッシュ、ファイルハンドル、typeglob。

スカラ型

スカラは唯一の基本型で、整数、浮動小数、文字列、リファレンスを保持できます。明示的な型宣言は不要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
my $n = 42; # 整数
my $f = 3.14; # 浮動小数点
my $s = "text"; # 文字列
my $r = \@ARGV; # リファレンス
my $u; # undef
# 文字とコードポイントの相互変換:
my $ch = chr(65); # "A"
my $ord = ord('A'); # 65
# 数値の基数:
my $hex = 0xFF; # 255
my $oct = 0b1010; # 10
# 大きな数値の精度:
# use bigint / Math::BigInt
# 型は代入に応じて自動で変わる

数値と文字列の相互変換

コンテキストに応じて自動変換:+ は数値、. は連結。明示的な変換は +0 と ."" を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use feature 'say';
# 自動変換:
my $sum = "3" + "4"; # 7(数値コンテキスト)
my $cat = 3 . 4; # "34"(文字列)
# 明示的な変換:
my $n = "42" + 0; # 文字列から数値へ
my $s = 42 . ""; # 数値から文字列へ
# 先頭数字のルール:
my $v = "12px" + 0; # 12
# 数字で始まらなければ 0:
my $z = "abc" + 0; # 0
# フォーマット出力:
my $out = sprintf "%.2f", 3.14159; # 3.14
# 数値と文字列では比較演算子が違う:
# == != < > 数値;eq ne lt gt 文字列

undef と defined

undef は未定義を表し、数値コンテキストでは 0、文字列コンテキストでは空文字列です。defined で判定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
my $x; # 代入しないと undef
say "未定义" unless defined $x;
# undef のコンテキスト値:
my $y = $x + 1; # 1(数値=0)
my $s = "值: $x"; # 值: (文字列=空)
# 判定:
if (defined $x) {
say "已定义";
}
# 定義されていなければ代入:
$x //= 5; # undef なら 5 を代入
say $x; # 5
# 明示的に undef へ:
undef $x;
# // は定義または演算子(5.10+)

真偽値

偽は undef、0、"0"、空文字列のみ。それ以外はすべて真です。論理演算子はオペランドを返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
# 偽値:undef、0、"0"、""
my @false = (undef, 0, "0", "");
# それ以外は真:
my @true = (1, "0.0", " ", [], {});
if ("0.0") { say "是真" } # 空でない文字列
if ([]) { say "数组引用真" }
# 論理演算子は最後に評価したオペランドを返す:
my $a = 0 || "fallback"; # "fallback"
my $b = 1 && "x"; # "x"
# 定義または:
my $c = $undef // "默认值"; # "默认值"
# ? : でブールを生成:
my $ok = 5 > 3 ? 1 : 0;

配列とリスト

リストリテラル (1,2,3)、qw 単語リスト、範囲 1..5。コンテキストがリスト全体か要素かを決定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
my @list = (1, 2, 3);
my @empty = ();
# qw ワードリスト:
my @names = qw(alice bob carol);
# 範囲:
my @digits = (1..5); # 1 2 3 4 5
my @chars = ('a'..'e'); # a b c d e
# リスト代入:
my ($a, $b, $c) = (1, 2, 3);
# ネストの展開:
my @flat = (1, (2, 3), 4); # 1 2 3 4
# コンテキストの違い:
my @x = (1, 2); # リスト、全部保持
my $y = (1, 2); # スカラー、末尾の 2
# スカラーコンテキストで配列の長さ:
my $len = @x; # 2

ハッシュのキーと値

ファットアロー => は左辺を自動でクォートします。キーと値は対で現れ、キーは文字列または整数です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
# ファットアローで自動クォート:
my %h = (name => 'Rex', age => 5);
# 等価な書き方:
my %h2 = ('name', 'Rex', 'age', 5);
# 数値キー:
my %num = (1 => 'one', 2 => 'two');
# 空ハッシュ:
my %empty = ();
# ハッシュをリストに:
my @kv = %h; # ('name','Rex','age',5)
# 混在した書き方:
my %mixed = (x => 1, 'y', 2);
# キーアクセスは波括弧:
my $name = $h{name};
# 波括弧内で式を使える:
my $key = 'age';
my $age = $h{$key};

ファイルハンドル

STDIN/STDOUT/STDERR は組み込みハンドル。open でレキシカルファイルハンドルを開き、3 引数形式が最も安全です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 組み込みハンドル:
print STDOUT "输出\n";
print STDERR "错误\n";
my $line = <STDIN>; # 入力の読み取り
# 3 引数 open:
open my $in, '<', 'data.txt' or die $!;
my $first = <$in>;
close $in;
# 書き込みハンドル:
open my $out, '>', 'out.txt' or die $!;
print $out "hello\n";
close $out;
# 端末かどうかを判定:
my $tty = -t STDOUT;
# ハンドルはスカラー、引数に渡せる:
sub dump_all {
my $fh = shift;
print $fh @_;
}

typeglob

* 接頭辞はシンボルテーブルのエントリを示し、スカラ/配列/サブルーチンの別名を作れます。モダンなコードではあまり使いません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
my $scalar = 1;
my @array = (1, 2);
# typeglob エイリアス:
*alias = \$scalar; # スカラーエイリアス
*array_alias = \@array;
# エイリアスは同じデータを指す:
$alias = 99;
say $scalar; # 99
# シンボルテーブルには同名スロットが含まれる:
# *name は $name @name %name &name を含む
# よくある用途:
# 1. 旧式ファイルハンドル:*FH = *STDOUT;
# 2. 関数エイリアス:*f = \&original;
# 現代はリファレンスを使うのが推奨:
my $r = \$scalar;
${$r} = 100;

4.リファレンスとデリファレンス

Perl はポインタの代わりにリファレンスを使います:バックスラッシュでアドレス取得、デリファレンス、アロー演算子、ネスト構造。

リファレンスの基礎

バックスラッシュ \ で変数のリファレンスを取得し、リファレンス型のスカラが得られます。ref 関数は型名を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @arr = (1, 2, 3);
my %h = (a => 1);
my $sref = \$name; # スカラーリファレンス
my $aref = \@arr; # 配列リファレンス
my $href = \%h; # ハッシュリファレンス
# ref は型名を返す:
say ref $sref; # SCALAR
say ref $aref; # ARRAY
say ref $href; # HASH
# リファレンスでなければ空文字列:
my $plain = 5;
say ref $plain; # ""
# リファレンスはスカラー、配列/ハッシュに格納可:
my @refs = ($aref, $href);

デリファレンス

@$ref、%$ref、$$ref はリファレンスを対応する型に戻します。波括弧形式 ${$ref} の方が視認性が高くなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my @arr = (10, 20, 30);
my %h = (a => 1, b => 2);
my $aref = \@arr;
my $href = \%h;
# 配列デリファレンス:
say $aref->[0]; # 10(推奨)
say ${$aref}[0]; # 10
# ハッシュデリファレンス:
say $href->{a}; # 1
say ${$href}{a}; # 1
# スカラーデリファレンス:
my $val = 99;
my $sref = \$val;
say $$sref; # 99
say ${$sref}; # 99
# 全体を取り出す:
my @copy = @$aref; # デリファレンスしてリストに
my %copy = %$href;
# 長さ/キーを取得:
my $n = scalar @$aref;

アロー演算子

-> はネスト構造をデリファレンスします:$ref->[0] や $ref->{key} のように層ごとに連結します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my $ref = [10, 20, 30];
say $ref->[0]; # 10
my $href = {name => 'Rex', age => 5};
say $href->{name}; # Rex
# 多段ネスト:
my $matrix = [[1, 2], [3, 4]];
say $matrix->[1][0]; # 3
my $users = [
{name => 'alice', age => 30},
{name => 'bob', age => 25},
];
say $users->[1]{name}; # bob
# 関数リファレンスの呼び出し:
my $fn = sub { "hi" };
say $fn->(); # hi
# 等価な波括弧の書き方:
say ${$users->[0]}{name}; # alice

無名リファレンス

[] は無名配列、{} は無名ハッシュ、sub {} は無名関数。名前付き変数を介さず直接構築できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 匿名配列:
my $aref = [1, 2, 3];
my $empty = [];
# 匿名ハッシュ:
my $href = {name => 'Rex'};
# 匿名関数:
my $fn = sub { "hello" };
# 直接ネストして構築:
my $config = {
name => 'app',
hosts => ['a.com', 'b.com'],
};
say $config->{hosts}[0]; # a.com
# 丸括弧はリスト、角括弧はリファレンス:
my @list = (1, 2, 3); # リスト
my $ref = [1, 2, 3]; # リファレンス
# 匿名リファレンスは変数を作らずに済む

ネスト構造

配列のハッシュ、ハッシュの配列などの複合構造。反復時は層ごとにデリファレンスします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 配列の配列(AoA):
my $grid = [[1, 2], [3, 4]];
say $grid->[0][1]; # 2
# ハッシュのハッシュ(HoH):
my $people = {
alice => {age => 30, city => 'NY'},
bob => {age => 25, city => 'LA'},
};
say $people->{bob}{city}; # LA
# ハッシュの配列(HoA):
my $grades = {
math => [90, 85],
eng => [88],
};
say $grades->{math}[0]; # 90
# ネストの走査:
for my $name (keys %$people) {
say "$name: $people->{$name}{age}";
}
# 深いネストは Data::Dumper で構造を確認できる

リファレンスによる引数渡し

リファレンス渡しで大きな配列のコピーを避け、呼び出し側のデータをその場で変更できます。関数からのリファレンス返却はより効率的です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
my @big = (1..1000);
# リファレンスで合計(配列をコピーしない):
sub total {
my $arr = shift;
my $sum = 0;
$sum += $_ for @$arr;
return $sum;
}
say total(\@big);
# 呼び出し元データをその場で変更:
sub bump {
my $arr = shift;
$_++ for @$arr;
}
bump(\@big);
say $big[0]; # 2(変更された)
# 複数のリファレンスを渡す:
sub merge {
my ($a, $b) = @_;
return [@$a, @$b]; # 匿名リファレンスを返す
}
my $m = merge([1, 2], [3, 4]);
say $m->[2]; # 3

bless とオブジェクト

bless はリファレンスにクラス名を刻印してオブジェクトにします。ref はそのクラス名を返します。オブジェクトは本質的にリファレンスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
package Animal;
sub new {
my $class = shift;
my %args = @_;
return bless { %args }, $class;
}
sub speak {
my $self = shift;
return "$self->{name} 叫了一声";
}
package main;
my $cat = Animal->new(name => '喵');
say $cat->speak; # 喵 叫了一声
# オブジェクトはクラスラベルの付いたハッシュリファレンス:
say ref $cat; # Animal
say $cat->{name}; # 喵
# 完全な OO は oop 節を参照

リファレンスの比較

リファレンスの比較は == でアドレス、ref で型判定。参照カウントがライフサイクルを管理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my @a = (1, 2);
my @b = (1, 2);
my $r1 = \@a;
my $r2 = \@a; # 同じ対象
my $r3 = \@b; # 別の対象
# リファレンスは == でアドレス比較:
say $r1 == $r2; # 1
say $r1 == $r3; # 0
# eq は文字列化した形式を比較:
say $r1 eq $r2; # 1
# ref で型を判定:
say ref $r1; # ARRAY
say ref \5; # SCALAR
say ref undef; # ""
# デリファレンス前に型を判定:
if (ref $href eq 'HASH') {
say $href->{name};
}
# リファレンスカウントは mem 節を参照

5.制御フロー

if/unless、後置修飾、for/foreach/while、next/last/redo。

if/elsif/else

if は真偽を判定、elsif はチェーン、unless は逆判定。条件の括弧は不要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $score = 85;
if ($score >= 90) {
say "优秀";
} elsif ($score >= 60) {
say "及格";
} else {
say "不及格";
}
# 偽値の判定(0/空/undef が偽):
my $name = "Rex";
if ($name) { say "有名字" }
# unless = if not:
my $debug = 0;
unless ($debug) { say "不开调试" }
# 波括弧は省略できない:
# if ($x) say 1; 構文エラー
# 式を条件にできる:
if (-f 'data.txt') { say "是文件" }

unless と until

unless は if の否定、until は while の否定。do-until は最低 1 回は実行されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
my $debug = 0;
# unless = if not:
unless ($debug) {
say "不输出调试信息";
}
# until = while not:
my $n = 0;
until ($n >= 3) {
say "n=$n";
$n++;
}
# 後置形式:
say "缺省" unless defined $config;
# do-until は最低 1 回実行:
do {
$n--;
} until ($n == 0);
# 反転条件に向いており、可読性が高い
# 複雑な条件は if/while を使う

後置修飾の制御

if/unless/for/while を単一文の後に置けます(Perl のイディオム)。1 文のみを修飾します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $n = 120;
# 後置修飾の文:
say "很大" if $n > 100;
die "超时" unless $done;
for my $i (1..5) {
say "第 $i 个";
}
# 後置 for:
print "$_ " for 1..3;
my $total = 0;
$total += $_ for @nums;
# 等価な前置の書き方:
# if ($n > 100) { say "很大" }
# for (@nums) { $total += $_ }
# 修飾できるのは 1 文のみ:
# say 1; say 2 if $c; if は say 2 だけを修飾
# 複数文は通常のブロックを使う

for/foreach ループ

for は C 風にもリスト反復にも書けます。foreach は別名。変数を指定しないと $_ が使われます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# C スタイル:
for (my $i = 0; $i < 5; $i++) {
say $i;
}
# リストを走査:
for my $item (1..3) {
say $item;
}
# 配列を走査:
my @names = qw(alice bob);
for my $name (@names) {
say $name;
}
# 変数を省略すると $_:
for (1..3) {
say "当前: $_";
}
# foreach は完全に等価:
foreach my $x (@names) {
say $x;
}
# 空リストならループ本体を実行しない

while ループ

while は条件が真の間実行します。ファイルを行ごとに読むには while が最も慣用的。do-while は先に 1 回実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
# ファイルを行単位で読む:
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
say $line;
}
# EOF で undef が返りループ終了
# 無限ループ:
my $done = 0;
while (1) {
last if $done;
$done = 1;
}
# 最低 1 回実行:
my $x = 10;
do {
say $x;
$x--;
} while ($x > 5);
# until は反転:
my $i = 0;
until ($i > 3) {
say "i=$i";
$i++;
}

next/last/redo

next は現在の周回をスキップ、last はループを抜ける、redo は現在の周回を再実行。ラベルでネストしたループを制御します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
# next はこの回をスキップ:
for my $i (1..10) {
next if $i % 2; # 奇数をスキップ
say $i;
}
# last はループを終了:
for my $i (1..10) {
last if $i > 5;
say $i;
}
# redo はこの回をやり直す:
my $sum = 0;
my $i = 0;
BLOCK: {
$i++;
$sum += $i;
redo BLOCK if $i < 3;
}
say $sum; # 6
# ラベルでネストしたループを制御:
OUTER: for my $x (1..3) {
for my $y (1..3) {
next OUTER if $y == 2;
say "$x,$y";
}
}

三項演算子と短絡評価

?: は条件式、|| と // はデフォルト値を提供、論理演算はオペランドを返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my $n = -3;
# 三項演算子:
my $sign = $n >= 0 ? "非负" : "负数";
say $sign;
# || デフォルト値(偽なら置換):
my $name;
my $shown = $name || "无名氏";
# // 定義または(undef のみ置換):
my $port;
$port //= 8080; # undef のときだけデフォルト
# 論理演算子はオペランドを返す:
my $a = 0 && die "不会执行";
my $b = 1 || die "不会执行";
# ネストした三項は可読性が悪い:
# my $g = $n>0 ? "正" : $n<0 ? "负" : "零";
# if/elsif に分解するのが推奨

goto とラベル

goto はラベルにジャンプできます(制限付き)。多階層の脱出にはラベル付き last を優先します。日常コードで goto はめったに使いません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 制限付き goto でラベルへ:
my $i = 0;
START:
$i++;
goto START if $i < 3;
say "结束: $i";
# goto はブロック内に飛び込めない
# ループ制御を優先:
my @items = (1, -1, 2);
for my $item (@items) {
last if $item < 0;
say $item;
}
# 多段脱出はラベル + last:
OUTER: for my $a (1..3) {
for my $b (1..3) {
last OUTER if $a * $b > 4;
say "$a x $b";
}
}
# 可読性を優先し、goto で飛び回らない

6.関数とサブルーチン

sub 定義、@_ 引数、戻り値、クロージャ、プロトタイプ、シグネチャ。

関数定義

sub は名前付きサブルーチンを定義し、最後の式が自動で返されます。呼び出しは括弧で引数を渡します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 定義と呼び出し:
sub greet {
return "hello";
}
say greet(); # hello
# 引数付き:
sub add {
my ($a, $b) = @_;
return $a + $b;
}
say add(2, 3); # 5
# 最後の式が自動的に返る:
sub square { my $x = shift; $x * $x }
say square(4); # 16
# 戻り値なし:
sub log_msg {
say "记录日志";
}
# 関数名は大文字小文字を区別する
# 未定義のまま呼ぶとコンパイル時にエラー

@_ と引数

引数リストは @_ にあります。shift で先頭を取得。@_ の要素は別名なので、その場で変更すると呼び出し元に波及します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
# @_ は引数リスト:
sub first_arg {
my $x = shift; # 先頭を取り出す
return $x;
}
# 複数の引数:
sub two {
my ($a, $b) = @_; # リスト代入
return "$a-$b";
}
say two(1, 2); # 1-2
# 関数内の shift はデフォルトで @_ を操作:
sub total {
my $sum = 0;
$sum += shift while @_;
return $sum;
}
say total(1, 2, 3); # 6
# @_ はエイリアス、変更が外部に漏れる:
sub bump_first { $_[0]++ }
my $x = 1;
bump_first($x);
say $x; # 2
# 通常はレキシカル変数にコピーしてから使う

戻り値

スカラまたはリストのどちらを返すかはコンテキスト次第です。wantarray で呼び出しコンテキストを判定。リファレンスを返してコピーを回避できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
sub coords { return (1, 2, 3) }
# リストコンテキストでは全部返す:
my @all = coords(); # (1,2,3)
# スカラーコンテキストでは最後を返す:
my $n = coords(); # 3
# 空の return:
sub nothing { return }
# undef を返す:
sub missing { return undef }
# wantarray でコンテキストを感知:
sub ctx {
return wantarray ? (1, 2) : 5;
}
my @l = ctx(); # (1,2)
my $s = ctx(); # 5
# 大きな構造はリファレンスを返してコピー回避:
sub make_list {
return [1, 2, 3]; # 匿名配列リファレンス
}
my $r = make_list();

無名サブルーチン

sub {} は関数リファレンスを作成し、->() で呼び出します。コールバック、配列やハッシュの保存に使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
# 匿名サブルーチン:
my $square = sub {
my $x = shift;
return $x * $x;
};
say $square->(5); # 25
# 引数として渡す(コールバック):
sub apply {
my ($fn, $val) = @_;
return $fn->($val);
}
say apply(sub { $_[0] * 2 }, 21); # 42
# 配列に格納:
my @handlers = (
sub { "one" },
sub { "two" },
);
say $handlers[1]->(); # two
# ハッシュに格納(ディスパッチテーブル):
my %ops = (
add => sub { $_[0] + $_[1] },
mul => sub { $_[0] * $_[1] },
);
say $ops{add}->(2, 3); # 5
# 関数リファレンスも普通のリファレンスと同じスカラー

クロージャ

クロージャはレキシカル変数を捕捉し、状態を記憶します。ファクトリ呼び出しごとに独立したコピーが得られます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# カウンタークロージャ:
sub make_counter {
my $count = 0;
return sub {
$count++; # 状態を覚える
return $count;
};
}
my $c = make_counter();
say $c->(); # 1
say $c->(); # 2
# それぞれ独立した状態:
my $a = make_counter();
my $b = make_counter();
$a->(); # 1
$b->(); # 1
# ファクトリ関数:
sub make_greeter {
my $prefix = shift;
return sub { "$prefix: $_[0]" };
}
my $hi = make_greeter("hi");
say $hi->("Rex"); # hi: Rex
# クロージャはコピーではなく変数そのものを捕まえる

プロトタイプ

プロトタイプはコンパイル時に引数のコンテキストを制約します(例:\@ は配列リファレンスを強制)。モダンなコードではシグネチャが推奨されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# プロトタイプで引数を制約:
sub myshift (\@) {
my $arr = shift;
return shift @$arr;
}
my @nums = (1, 2, 3);
my $first = myshift(@nums); # 自動でリファレンス化
say $first; # 1
# よく使うプロトタイプ:
# ($) スカラーコンテキストを強制
# (\@) 配列リファレンスを強制
# (\%) ハッシュリファレンスを強制
# ($@) スカラー 1 つと配列
# プロトタイプはメソッド呼び出しには無効:
# $obj->meth(@args) プロトタイプを無視
# プロトタイプは誤解を招くことがある
# 5.36+ ではシグネチャを推奨:
sub real($a, $b) { $a + $b }

関数シグネチャ

feature 'signatures' は宣言的な引数を提供します(5.36+ で安定)。デフォルト値や配列引数にも対応します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
# 宣言的な引数:
sub add($a, $b) {
return $a + $b;
}
say add(2, 3); # 5
# デフォルト値:
sub greet($name = "world") {
return "hello, $name";
}
say greet(); # hello, world
# 複数引数 + 配列:
sub config($name, @rest) {
return ($name, @rest);
}
# 引数はレキシカル変数、@_ に触れない:
sub swap($x, $y) {
return ($y, $x);
}
my ($u, $v) = swap(1, 2);
# シグネチャは shift より明確
# 名前付き引数は非対応、ハッシュで代用

関数リファレンス

\&sub で関数リファレンスを取得し、->() や &$ref() で呼び出します。コールバック、ディスパッチテーブル、高階関数に使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 関数リファレンスを取得:
sub twice { $_[0] * 2 }
my $fn = \&twice;
say $fn->(10); # 20
say &$fn(10); # 20
# 引数として渡す:
sub run {
my $code = shift;
$code->();
}
run(sub { say "运行" });
# コードリファレンスかどうか確認:
my $cb = \&twice;
say ref $cb; # CODE
# 末尾呼び出し最適化(呼び出しフレームを再利用):
sub loop {
return if $_[0] <= 0;
@_ = ($_[0] - 1);
goto &loop;
}
loop(100000);
# 深い再帰は goto &sub を優先

7.文字列

クォート、補間、連結、フォーマット、部分文字列、エンコーディング処理。

文字列リテラル

シングルクォートはそのまま、ダブルクォートは補間。q()/qq() は等価。隣接する文字列は自動で連結されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
# シングルクォートは挿入しない:
my $a = '原样 $name';
# ダブルクォートは挿入:
my $b = "hi $name";
# q/qq 演算子(任意のデリミタ):
my $c = q(原样 $name);
my $d = qq(插值 $name);
# シングルクォート内の \\ と \' エスケープ:
my $e = 'it\'s ok';
# 隣接するリテラルは自動で連結:
my $f = 'a' 'b' 'c'; # abc
# 改行とタブ:
my $g = "第一行\n第二行\t制表";
# 引用符のネストはエスケープに注意:
my $h = "他说 \"你好\"";

補間のルール

ダブルクォート内では $scalar と @array が補間されます。波括弧で境界を区切り、添字/デリファレンスも補間できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @nums = (1, 2, 3);
# スカラー挿入:
my $a = "你好, $name";
# 配列挿入(空白区切り):
my $b = "数组: @nums"; # 数组: 1 2 3
# 波括弧で変数境界を区切る:
my $s = "${name}s house";
# インデックス挿入:
my @h = qw(x y z);
my $c = "第二个: $h[1]"; # y
# デリファレンス挿入:
my $user = {name => 'Rex'};
my $d = "名字: $user->{name}";
# エスケープ:
my $e = "换行\n制表\t";
# \\ バックスラッシュ \$ リテラルの $
# 変数名に非単語文字が含まれる場合は波括弧

連結と繰り返し

. は文字列連結、x は繰り返し、join は配列連結。length で長さ、reverse で反転。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# ドット連結:
my $full = "Hello" . " " . "World";
# 数字はドットコンテキストで文字列になる:
my $s = 1 . 2; # "12"
# 繰り返し演算子:
my $line = "-" x 20; # 20 個のハイフン
# join で配列を連結:
my @parts = ('a', 'b', 'c');
my $csv = join ",", @parts; # a,b,c
# 長さ:
my $len = length "hello"; # 5
# 反転:
my $rev = reverse "abc"; # cba
# 文字列比較:
"a" eq "a"; # 等しい
"a" lt "b"; # より小さい(辞書順)
"abc" cmp "abd"; # 比較結果
# 連結は .= で変数に追加:
my $buf = "x";
$buf .= "y"; # xy

部分文字列と検索

substr で部分文字列の取得/変更、index/rindex で位置検索(見つからない場合は -1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my $s = "Hello, World";
# 部分文字列の取得:
my $sub = substr $s, 0, 5; # Hello
my $from = substr $s, 7; # World
my $last4 = substr $s, -5; # World(末尾から)
# 位置の検索:
my $pos = index $s, "World"; # 7
my $last = rindex $s, "o"; # 8(右から探す)
# 見つからないと -1 を返す:
index $s, "zzz"; # -1
# 部分文字列の置換:
my $t = $s;
substr($t, 7, 5) = "Perl"; # Hello, Perl
# 行単位の分割は split:
my @words = split /,/, $s;
# より複雑な抽出は正規表現(regex 節参照)

chomp と split

chomp は末尾改行を削除、split は区切り文字でリストに分割、join はその逆操作です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# chomp は末尾の改行を除去:
my $line = "abc\n";
chomp $line; # "abc"
# chomp は破壊的変更で,除去した文字数を返す
# split は区切り文字で分割:
my @f = split /,/, "a,b,c"; # a b c
my @w = split /\s+/, "a b"; # a b
# 区切り文字を省略すると空白で分割:
my @t = split /\s+/, "a b c";
# 分割数を制限:
my @two = split /,/, "a,b,c", 2; # a, "b,c"
# join は逆の操作:
my $back = join "-", @f; # a-b-c
# 文字列を行配列に変換:
my @lines = split /\n/, $text;
# CSV 解析は Text::CSV モジュールを推奨

printf/sprintf

printf は直接出力、sprintf は文字列を返します。%d/%s/%f などの書式でゼロ埋めや整列を行います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
# printf は直接出力:
printf "%s 今年 %d 岁\n", "Rex", 5;
# sprintf は文字列を返す:
my $s = sprintf "%05d", 42; # 00042
# よく使う書式:
# %s 文字列 %d 整数 %f 浮動小数点
# %x 16進数 %o 8進数 %e 科学表記
# %.2f 小数2桁 %5d 右詰め空白埋め
# %-5d 左詰め %05d ゼロ埋め
my $money = sprintf "%.2f", 3.14159; # 3.14
my $hex = sprintf "%x", 255; # ff
# 動的幅:
my $w = 10;
my $padded = sprintf "%${w}s", "hi";
# ハンドルへの出力:
printf STDOUT "值: %d\n", 42;
# 引数は順にマッチし,複数引数も対応

大文字小文字とトリミング

uc/lc/ucfirst で大文字小文字を変更、置換正規表現で空白を除去、tr/// は文字単位の変換です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my $s = "Hello World";
my $upper = uc $s; # HELLO WORLD
my $lower = lc $s; # hello world
my $cap = ucfirst $s; # Hello World
my $w = lcfirst "Hello"; # hello
# 先頭・末尾の空白除去(正規表現):
my $t = " padded ";
$t =~ s/^\s+//; # 先頭を除去
$t =~ s/\s+$//; # 末尾を除去
# プレフィックス/サフィックス判定:
$s =~ /^Hello/;
$s =~ /World$/;
# 文字の変換:
my $u = "abc";
$u =~ tr/a-z/A-Z/; # ABC
# 削除を伴う変換:
my $d = "a1b2c3";
$d =~ tr/0-9//d; # abc
# 文字とコードポイント:
ord 'A'; # 65
chr 65; # "A"

エンコーディングと UTF-8

use utf8 でソースを UTF-8 として宣言、encode/decode でコード変換、エンコーディング層付きでファイルを読み書きします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use utf8; # ソースを UTF-8 として解釈
use Encode;
my $chinese = "中文";
binmode STDOUT, ':utf8'; # UTF-8 で出力
# エンコードとデコード:
my $bytes = encode('UTF-8', $chinese); # バイト列
my $back = decode('UTF-8', $bytes); # 文字列
# 文字数とバイト数:
my $text = "你好";
length $text; # 2(文字)
length encode('UTF-8', $text); # 6(バイト)
# エンコーディング層を付けて読み書き:
open my $fh, '<:utf8', 'file.txt' or die $!;
open my $out, '>:utf8', 'out.txt' or die $!;
# JSON は UTF-8 を使用:
use JSON::PP;
my $j = JSON::PP->new->utf8->encode({k => "中文"});

8.コレクションとデータ構造

配列/ハッシュ操作、スライス、map/grep、ソート、List::Util。

配列の追加・削除・変更

push/pop/shift/unshift の 4 端点操作、splice で中間の挿入/削除、reverse/sort。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3);
# スタック操作:
push @nums, 4; # 末尾に追加
my $top = pop @nums; # 末尾をポップ
unshift @nums, 0; # 先頭に追加
my $head = shift @nums; # 先頭をシフト
# 途中への挿入/削除:
splice @nums, 1, 0, 99; # 添字1 に 99 を挿入
splice @nums, 0, 1; # 添字0 を削除
# 反転とソート:
my @rev = reverse @nums;
my @sorted = sort @nums;
# 結合:
my @all = (@nums, (4, 5));
# 末尾に複数追加:
push @nums, 6, 7;
# 配列の長さと最後の要素:
my $len = scalar @nums;
my $end = $nums[-1];

スライス

@arr[...] で配列スライス、%hash{...} でハッシュスライスを取得。スライスは一括代入できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my @arr = qw(a b c d e);
# 配列スライス:
my @mid = @arr[1..3]; # b c d
my @sel = @arr[0, 2, 4]; # a c e
# スライスは代入可能:
@arr[1, 2] = qw(X Y);
my %h = (alice => 30, bob => 25, carol => 28);
# ハッシュスライス(複数キーの値を取得):
my @ages = @h{qw(alice bob)}; # 30 25
# ハッシュスライスへの代入:
@h{qw(dan eve)} = (22, 35);
# 配列/ハッシュ全体:
my @all = @arr;
# リファレンス上のスライス:
my $r = \@arr;
my @sub = @$r[1..2];
# スカラーコンテキストでは数を返す:
my $n = @arr[1..3]; # 3

リスト演算

sum/min/max は List::Util から。grep で重複除去、範囲抽出、リスト分解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
use List::Util qw(sum min max);
my @nums = (3, 1, 4, 1, 5);
my $sum = sum @nums; # 14
my $min = min @nums; # 1
my $max = max @nums; # 5
# 重複除去(初出順を保持):
my %seen;
my @uniq = grep { !$seen{$_}++ } @nums;
# 結合:
my @a = (1, 2);
my @b = (3, 4);
my @ab = (@a, @b);
# リストの分解:
my ($x, $y) = @nums; # 3, 1
my ($head, @rest) = @nums;
# ソートして先頭3つを取得:
my @top3 = (sort { $b <=> $a } @nums)[0..2];
# 戻り値を無視するときは一時配列のオーバーヘッドに注意

map と grep

grep はフィルタ、map は変換で、いずれも $_ で反復します。grep をスカラコンテキストで使うとヒット件数を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3, 4, 5);
# grep でフィルタ:
my @big = grep { $_ > 2 } @nums; # 3 4 5
my @even = grep { $_ % 2 == 0 } @nums; # 2 4
# map で変換:
my @sq = map { $_ * $_ } @nums; # 1 4 9 16 25
my @labels = map { "第 $_ 个" } @nums;
# スカラーコンテキストではヒット数を返す:
my $count = grep { $_ > 3 } @nums; # 2
# map でハッシュを生成:
my %square = map { $_ => $_ * $_ } 1..5;
# 組み合わせ:
my @big_sq = map { $_ * $_ }
grep { $_ > 2 } @nums; # 9 16 25
# grep/map は元の配列を変更しない
# マッピング結果を使わないと無駄になる

ハッシュの反復

keys/values でキー/値リスト、each でキーと値の対を反復。exists/delete で管理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my %h = (a => 1, b => 2, c => 3);
# キー / 値のリスト:
my @keys = keys %h;
my @values = values %h;
# キーと値のペア走査(each はカーソルを消費):
while (my ($k, $v) = each %h) {
say "$k=$v";
}
# each を呼ぶたびに次のペアを取得
# each の多重ネストは誤りやすい。単層で使う
# 存在確認と削除:
exists $h{a}; # 1
delete $h{b};
# クリア:
%h = ();
# 結合:
my %m = (d => 4, %h);
# キーと値を反転:
my %inv = reverse %m;
# カウントは each + ++ が定番:
$count{$_}++ for @words;

ハッシュのイディオム

ハッシュはカウント、重複除去、集合、キャッシュ、グループ化に使えます。反復中の追加/削除には注意が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use feature 'say';
# カウント:
my @words = qw(a b a c b a);
my %count;
$count{$_}++ for @words; # a=>3 b=>2 c=>1
# 重複除去:
my %seen;
my @uniq = grep { !$seen{$_}++ } @words;
# 集合判定:
my %set = map { $_ => 1 } qw(a b c);
say "存在" if $set{a};
# キャッシュ:
my %cache;
sub expensive {
my $key = shift;
return $cache{$key} if exists $cache{$key};
$cache{$key} = $key * $key;
return $cache{$key};
}
# グループ化(先頭文字で):
my %groups;
for my $word (@words) {
my $first = substr $word, 0, 1;
push @{ $groups{$first} }, $word;
}
# 配列値のハッシュはリファレンスを格納し,読むときはデリファレンスが必要

ソート

sort はデフォルトで文字列順。比較ブロックで <=> は数値、cmp は文字列比較です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
my @nums = (5, 3, 8, 1);
# デフォルトは文字列ソート:
my @s = sort @nums; # 1 3 5 8
# 数値の昇順/降順:
my @n = sort { $a <=> $b } @nums; # 昇順
my @d = sort { $b <=> $a } @nums; # 降順
# 文字列比較:
my @w = sort { $a cmp $b } qw(banana apple);
# 長さ順:
my @len = sort { length $a <=> length $b }
@words;
# 安定ソート(副次キー):
my @st = sort {
length $a <=> length $b || $a cmp $b
} @words;
# キー順にソートしてハッシュを走査:
my %h = (b => 2, a => 1);
for my $k (sort keys %h) {
say "$k=$h{$k}";
}
# 比較ブロックの $a/$b は要素であり,添字ではない

List::Util

sum/sum0/min/max/first/reduce/any/all/shuffle。sum0 は空リストで 0 を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
use List::Util qw(
sum sum0 min max first reduce shuffle any all
);
my @nums = (3, 1, 4, 1, 5);
my $sum = sum @nums; # 14
my $sum0 = sum0 @nums; # 空リストは 0
my $min = min @nums; # 1
my $max = max @nums; # 5
# first は最初に条件を満たすもの:
my $f = first { $_ > 3 } @nums; # 4
# any / all:
my $has = any { $_ > 4 } @nums; # true
my $all = all { $_ > 0 } @nums; # true
# reduce で畳み込み:
my $prod = reduce { $a * $b } @nums;
# shuffle でシャッフル:
my @mix = shuffle @nums;
# リストをまたいで最値を求める:
min @a, @b;
# 注意:sum/min は空リストで undef を返す

9.メモリとパフォーマンス

参照カウント、循環参照、弱参照、自動有効化(autovivification)、パフォーマンスベンチマーク。

参照カウント

Perl は参照カウントで自動メモリ解放を行い、手動の free は不要です。カウントが 0 になると解放されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
# 参照カウントで自動管理:
my $ref = [1, 2, 3]; # カウント 1
my $copy = $ref; # カウント 2
undef $copy; # カウント 1
undef $ref; # カウント 0,解放
# レキシカル変数はスコープを出ると即回収:
{
my $tmp = "数据";
} # $tmp はここで解放
# 手動 malloc/free は不要:
# 作成だけして,回収はエンジン任せ
# C/Rust の明示管理とは異なる
# メモリピークの観察:
# 大きな配列は処理後に undef で早めに解放
# またはストリーム処理に切り替え,まとめて読み込まない
# 循環参照はカウントでは回収できない。次節参照

循環参照

相互に参照し合うとカウントが 0 にならずメモリリークします。弱参照または手動で循環を断ち切ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 循環参照でリーク:
my $a = {};
my $b = {};
$a->{other} = $b; # a は b を参照
$b->{other} = $a; # b は a を参照
# カウントが互いに保持され,2 つのメモリは永遠に解放されない
# 自己参照も同様にリーク:
my $node = {};
$node->{self} = $node;
# 解決策 1:手動で環を断つ:
$a->{other} = undef;
# 解決策 2:弱参照:
use Scalar::Util 'weaken';
my $wa = {};
my $wb = {};
$wa->{other} = $wb;
weaken $wa->{other}; # 強参照しない
# $wb は正常に回収される
# 検出:Devel::Leak / valgrind
# データ構造はなるべく DAG(非循環)にする

弱参照

Scalar::Util::weaken はリファレンスがカウントを増やさないようにします。対象が解放されると弱参照は undef になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
use Scalar::Util qw(weaken isweak);
# 強参照 + 弱参照:
my $data = {name => 'Rex'};
my $strong = $data; # 強参照
my $weak = $data;
weaken $weak; # 弱参照に変換
say isweak($weak); # 1
# 強参照を解放:
undef $data;
undef $strong;
# 弱参照は解放済みメモリを指す:
say defined $weak; # 0(undef に変化)
# キャッシュは回収を妨げない:
my %cache;
my $obj = {id => 1};
$cache{1} = $obj;
weaken $cache{1};
# 使用前に空か確認:
if (defined $cache{1}) {
say "命中缓存";
}
# 弱参照はオブザーバー/キャッシュ用途でよく使う

スコープ解放

レキシカル変数とハンドルはスコープを抜けると自動で解放/クローズされます。undef を使えば早めに解放できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 関数から戻るとローカル変数は解放:
sub work {
my $big = "x" x 1000000; # 大きな文字列
process($big);
return 1;
# $big は戻り時に解放
}
# ブロックで早期にライフサイクルを終了:
{
my $tmp = load_data();
process($tmp);
} # $tmp はブロック終了時に解放
# ファイルハンドルは自動で閉じる:
sub read_all {
open my $fh, '<', 'data.txt' or die $!;
local $/;
return <$fh>; # 戻り時に $fh が閉じる
}
# 明示的な解放:
undef @big_array; # 即解放
# 参照されたデータは参照が生きている間保持:
my $keep = [1, 2, 3];
my $holder = {list => $keep}; # $keep は生存

自動有効化(autovivification)

未定義のリファレンスに代入すると自動でコンテナが作成されます。書き込みは便利ですが、ネスト読み出しで意図せず作られることがあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 書き込み時に自動生成:
my $hash;
$hash->{a} = 1; # ハッシュリファレンスを自動生成
say $hash->{a}; # 1
my $arr;
$arr->[0] = "x"; # 配列リファレンスを自動生成
# 多段で自動生成:
my $deep;
$deep->{a}[0]{b} = 1; # 段階的に生成
# 存在しないキーの読み取りは undef:
my %h;
say defined $h{missing} ? "有" : "无";
# 読み取りのネストでも中間層が作られることがある:
my $outer;
my $v = $outer->{x}{y}; # {x} が作られる
# 短絡評価で空判定を避ける:
if ($outer && $outer->{x}) {
say "有值";
}
# 完全に無効化したい場合:
# use no autovivification;

アロケーションと高速化

配列の事前確保、正規表現の事前コンパイル、補間連結を避ける。計測してから最適化します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 配列容量の事前確保:
my @nums;
$#nums = 9999; # 事前確保
for my $i (0..9999) {
$nums[$i] = $i;
}
# 正規表現のプリコンパイルを再利用:
my $re = qr/\d{4}-\d{2}-\d{2}/;
for my $line (@lines) {
$line =~ $re; # コンパイル結果を再利用
}
# 連結は join 優先:
my $big = join '', @parts;
# 大量の .= 連結は遅い:
my $s = "";
$s .= "x" for 1..1000;
# プレーンテキストはシングルクォート:
print '纯文本';
# ハッシュで O(1) 検索:
my %lookup = map { $_ => 1 } @list;
# 解析は Devel::NYTProf
# まず計測してから最適化。闇雲な微調整はしない

メモリ計測

Devel::Size で構造体サイズを計測、ピークメモリを観察、大きなファイルはストリーム処理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 構造体のメモリを計測:
use Devel::Size qw(total_size);
my $struct = {
name => 'Rex',
tags => [qw(dev tools)],
};
say total_size($struct); # バイト数
# 単一の変数:
Devel::Size::size($struct);
# ブロック単位で大きいオブジェクトを特定:
# まずトップレベルを dump してからブロック単位で比較
# プロセスメモリ監視(Linux):
# /proc/$$/status の VmRSS を読む
# Windows はタスクマネージャーで観察
# 大きなファイルはストリーム処理:
open my $fh, '<', 'big.log' or die $!;
while (my $line = <$fh>) {
process($line); # まとめて読み込まない
}
# 参考:perldoc Devel::Size

パフォーマンスベンチマーク

Benchmark モジュールで実装を比較、Time::HiRes で高精度計測。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
use Benchmark qw(cmpthese);
my @data = (1..1000);
# 2 つの書き方を比較:
cmpthese(1000, {
join_way => sub {
my $s = join ',', @data;
},
concat_way => sub {
my $s = "";
$s .= "$_" for @data;
},
});
# 毎秒の実行回数と相対速度を出力
# 秒単位の計測:
my $t0 = time;
work();
my $elapsed = time - $t0;
say "耗时 $elapsed 秒";
# 高精度計測:
use Time::HiRes qw(time);
my $t1 = time;
work();
my $ms = (time - $t1) * 1000;
say "耗时 $ms 毫秒";
# ホットスポットの解析は Devel::NYTProf

10.オブジェクト指向

bless リファレンス、メソッド、継承、アクセサ、演算子オーバーロード、Moose/Moo。

bless による構築

bless はリファレンスにクラス名を刻印してオブジェクトにします。オブジェクトは本質的にクラス名を持つリファレンスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
package Person;
sub new {
my $class = shift;
my $self = { name => '未知' };
return bless $self, $class;
}
# オブジェクトはタグ付きリファレンス:
my $p = Person->new();
say ref $p; # Person
# クラス名はデフォルトで現在のパッケージ:
sub new_short {
my $self = {x => 1};
return bless $self; # クラスは現在のパッケージ
}
# オブジェクトは本質的にハッシュリファレンス:
$p->{name} = 'Rex'; # フィールドを直接変更
# メソッドにカプセル化するのが推奨(アクセサ参照)
# 他の型のリファレンスも bless 可能:
# bless [], 'Stack'; bless \$x, 'Ref'

メソッドと呼び出し

-> の左がオブジェクト、右がメソッド。メソッドの第 1 引数は $self です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
package Counter;
sub new {
my $class = shift;
return bless { count => 0 }, $class;
}
sub inc {
my $self = shift;
$self->{count}++;
return $self->{count};
}
sub value {
my $self = shift;
return $self->{count};
}
package main;
my $c = Counter->new(); # コンストラクタ呼び出し
$c->inc(); # メソッド呼び出し
$c->inc();
say $c->value(); # 2
# メソッドの第一引数はオブジェクト:
# 関数形式の呼び出しと等価:
Counter::value($c); # 2
# メソッドはクラスと親クラスから継承
# -> はチェーン可能:$c->inc()->inc()

継承

use parent は親クラスを宣言します。サブクラスは同名のメソッドをオーバーライドでき、親メソッドを再利用できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
package Animal;
sub new {
my $class = shift;
my %a = @_;
return bless { name => $a{name} }, $class;
}
sub speak {
my $self = shift;
return "$self->{name} 叫";
}
sub eat {
my ($self, $food) = @_;
return "$self->{name} 吃 $food";
}
package Dog;
use parent -norequire, 'Animal';
sub speak { # 親メソッドをオーバーライド
my $self = shift;
return "$self->{name} 汪汪";
}
package main;
my $d = Dog->new(name => '旺财');
say $d->speak; # 旺财 汪汪
say $d->eat('骨头'); # 継承した eat
# メソッド探索:オブジェクトクラス → 親クラスチェーン
# 多重継承は @ISA か use parent を使う

コンストラクタ

new はクラス名と引数を受け取り、bless されたオブジェクトを返します。複数のコンストラクタを定義できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
package Point;
sub new {
my ($class, %args) = @_;
my $self = {
x => $args{x} // 0,
y => $args{y} // 0,
};
return bless $self, $class;
}
sub coords {
my $self = shift;
return ($self->{x}, $self->{y});
}
# 変種:リストから構築:
sub from_list {
my ($class, $pair) = @_;
return $class->new(x => $pair->[0],
y => $pair->[1]);
}
package main;
my $p = Point->new(x => 3, y => 4);
my ($x, $y) = $p->coords;
say "$x,$y"; # 3,4
# フィールドのデフォルト値とバリデーションは new に置く
# 名前付き引数はファットカンマの方が明確

カプセル化とアクセサ

フィールドアクセスをメソッドにカプセル化し、書き込みアクセサには検証を入れます。外部から直接フィールドを変更されるのを防げます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
package Account;
sub new {
my ($class, $name) = @_;
return bless {
name => $name,
money => 0,
}, $class;
}
# 読み取りアクセサ:
sub name { my $s = shift; return $s->{name} }
sub money { my $s = shift; return $s->{money} }
# 書き込みアクセサ(バリデーション付き):
sub deposit {
my ($self, $amt) = @_;
die "金额必须为正" if $amt <= 0;
$self->{money} += $amt;
}
package main;
my $acc = Account->new('alice');
$acc->deposit(100);
say $acc->name; # alice
say $acc->money; # 100
# 意味的バリデーションはメソッド内に集中
# $acc->{money} を直接変更するとバリデーションを回避してしまう

演算子オーバーロード

use overload は +、"" などの演算子を定義し、オブジェクトが数値や文字列として演算できるようにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
package Money;
use overload (
'""' => 'as_string', # 文字列化
'+' => 'add', # 加算
'fallback' => 1,
);
sub new {
my ($class, $cents) = @_;
return bless { cents => $cents }, $class;
}
sub as_string {
my $self = shift;
return sprintf %.2f", $self->{cents} / 100;
}
sub add {
my ($a, $b) = @_;
return Money->new($a->{cents} + $b->{cents});
}
package main;
my $m1 = Money->new(199);
my $m2 = Money->new(1);
say $m1 + $m2; # ¥2.00
say "$m1"; # ¥1.99
# オーバーロード可能:+ - * / <=> cmp など
# 真偽値/リファレンス比較の意味に注意

Moo/Moose

Moo は軽量なオブジェクトシステム:has で属性を宣言、アクセサ自動生成、extends で継承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# Moo(軽量、小規模プロジェクトに推奨):
package Employee;
use Moo;
has name => (is => 'ro', required => 1);
has salary => (is => 'rw', default => 0);
sub annual {
my $self = shift;
return $self->salary * 12;
}
package Manager;
use Moo;
extends 'Employee';
has team_size => (is => 'ro', default => 0);
package main;
my $e = Employee->new(name => 'Rex');
$e->salary(5000);
say $e->name; # Rex
say $e->annual; # 60000
# Moo はアクセサを自動生成
# is => 'ro' は読み取り専用,'rw' は読み書き
# Moose は完全版(型/ロール/メタプログラミング)
# スクリプトは Moo,大規模プロジェクトは Moose

ロールとコンポジション

Moo::Role は再利用可能なメソッド集合を定義し、with で取り込みます。継承よりコンポジションが推奨されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# ロールは再利用可能なメソッド集合:
package Walkable;
use Moo::Role;
sub walk { "行走中" }
package Car;
use Moo;
with 'Walkable'; # ロールを取り込む
sub drive { "驾驶中" }
package main;
my $car = Car->new;
say $car->walk; # 行走中
say $car->drive; # 驾驶中
# 複数ロールの組み合わせ:
# with 'Walkable', 'Drivable';
# 同名メソッドは with の順で後のものが勝つ
# ロールはダイヤモンド継承問題を回避
# Moose のロールも考え方は同じ
# 純手書きならハッシュにサブルーチンを格納して模倣可能
# 共有動作は継承よりロールを優先

11.エラー処理

die/warn、eval での捕捉、Try::Tiny、Carp、独自例外。

die と warn

warn は警告を表示して終了せず、die は表示して終了します。die は文字列またはオブジェクトを投げられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# warn は STDERR に出力し,終了しない:
warn "警告:配置缺失\n";
# die は出力して終了(非 0 ステータス):
# die "致命错误\n";
# die 後は後続コードは実行されない
# die は eval 内で捕捉できる:
eval {
die "可捕获的错误\n";
};
say "捕获: $@" if $@;
# die はオブジェクト/リファレンスも渡せる:
die { code => 500, msg => "server error" };
# デフォルト終了コード:
# die は $! か 255 を使う;exit N でカスタム
# 常識:$! には直近のシステムエラーが入る
# 本番コード:die の前に $! を含めて原因追跡しやすく

eval での捕捉

eval BLOCK は例外を捕捉し、失敗時は $@ を設定。成功時はブロックの最後の式を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# eval BLOCK で例外を捕捉:
eval {
die "boom";
};
if ($@) {
say "捕获: $@";
}
# 成功時は $@ が空
# eval の戻り値:
my $result = eval {
compute(); # 例外なら undef を返す
};
# エラー時は undef を返す
# ネストした eval で $@ を保存:
my $outer_err;
eval {
eval { die "内层" };
$outer_err = $@;
};
# 文字列 eval にはセキュリティ問題がある:
# eval $code; # 非推奨
# 例外捕捉には eval BLOCK のみ使う
# 構文エラーはコンパイル時に露見する

$@ の処理

$@ は直近のエラーを保持。文字列マッチやオブジェクト型チェックで判定し、undef と例外を区別します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# $@ には最近のエラーが入る:
eval { die "出错啦" };
my $err = $@; # 保存してから使う
say $err if $err;
# 内容でマッチ:
eval { die "timeout" };
if ($@ && $@ =~ /timeout/) {
say "超时处理";
}
# die にオブジェクトを渡すと $@ はオブジェクト:
eval {
die bless {msg => 'x'}, 'MyError';
};
if (ref $@ eq 'MyError') {
say $@->{msg};
}
# 結果の undef と例外を区別:
my $ok = eval { compute() };
if ($ok) { say "成功" }
# $@ はグローバル変数なので,呼び出しをまたいで依存しない
# モダンな書き方は Try::Tiny で $_ を隔離

Try::Tiny

try/catch/finally は構造が明確で、$_ がエラーを運び、$@ の競合を解決します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use Try::Tiny;
# try/catch:
try {
die "boom";
} catch {
say "捕获: $_"; # エラーは $_ に入る
};
# 戻り値:
my $result = try {
compute();
} catch {
warn "失败: $_";
undef; # エラー時は undef を返す
};
# 最後の後始末:
try {
risky();
} catch {
say "出错";
} finally {
cleanup(); # 常に実行される
};
# コンテキスト保持:
my @rows = try { fetch_rows() };
# Try::Tiny は捕捉にローカルな $_ を使う
# $@ がネストした eval で上書きされるのを防ぐ
# コアモジュール JSON などのエラーも捕捉可能

Carp によるエラー

croak/carp は呼び出し元の位置を、confess/cluck はスタック付きで報告。ライブラリコードで親切です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use Carp qw(carp croak confess);
# croak は呼び出し元の位置を報告:
sub open_data {
croak "无法打开" unless -e "data.txt";
}
open_data(); # エラーは呼び出し行を指す
# carp は警告(呼び出し元を報告):
sub deprecated { carp "已废弃" }
# confess は完全なスタックを出力:
sub inner { confess "内部错误" }
sub outer { inner() }
# 違い:
# die/warn 現在の行を報告
# croak/carp 呼び出し元の行を報告
# confess/cluck 呼び出し元 + スタック
# ライブラリは croak を使うと利用者が特定しやすい

カスタム例外

オブジェクトを die し、ref/isa で判定。"" をオーバーロードして文字列化を親切にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
package MyError;
sub new {
my ($class, %args) = @_;
return bless { %args }, $class;
}
sub as_string {
my $self = shift;
return "$self->{code}: $self->{msg}";
}
use overload ('""' => 'as_string');
package main;
sub risky_thing {
my $ok = shift;
die MyError->new(code => 500, msg => "失败")
unless $ok;
}
# 投げると捕捉:
eval { risky_thing(0) };
if (ref $@ && $@->isa('MyError')) {
say "错误码: $@->{code}"; # 500
}
# 簡易的な方法:
# use Exception::Class;
# または Ouch モジュールでワンライナー的に投げる
# オブジェクト例外は構造化情報を運べる

autodie

autodie は open などのシステムコール失敗時に自動で die するため、手書きの or die を省略できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use autodie;
# オープン失敗で自動的に die:
open my $fh, '>', 'out.txt';
# or die $! は不要になる
# よく使うシステムコールをカバー:
# open close chdir system exec
# chmod mkdir rmdir rename unlink
# 範囲を指定:
use autodie qw(open);
# 自動チェックを無効化:
no autodie;
# eval と組み合わせて捕捉:
eval {
open my $f, '<', 'missing.txt';
};
if ($@) { say "打开失败" }
# Try::Tiny と組み合わせるとより良い:
use Try::Tiny;
try {
open my $f, '<', 'x.txt';
} catch {
say "无法打开: $_";
};
# autodie はモダンな Perl に同梱(2.30+)

エラー処理のパターン

or die のイディオム、事前の検証、失敗時の早期 return。トップレベルは die、ライブラリ内部は return を使い分けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use strict;
use warnings;
use feature 'say';
# or die の慣用句:
open my $fh, '<', 'data.txt'
or die "打开失败: $!";
# 事前に引数を検証:
sub divide {
my ($a, $b) = @_;
die "除数为零" unless $b;
return $a / $b;
}
# 例外でなくステータスを返す:
sub parse {
my $s = shift;
return $s =~ /^\d+$/ ? $s + 0 : undef;
}
my $n = parse("12");
if (defined $n) {
say "数字: $n";
} else {
say "非法输入";
}
# 失敗したら早期リターン:
sub load_config {
my $path = shift;
return undef unless -f $path;
open my $f, '<', $path or return undef;
local $/;
return <$f>;
}
# トップレベルは die,ライブラリ内部は返り値中心

12.ファイル I/O

open による読み書き、ダイヤモンド演算子、スラープ、エンコーディング、パス、JSON。

オープンと読み込み

3 引数 open でファイル読み込み、行ごと while が最も省メモリ。-e/-f でファイル属性を判定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 3 引数の open(推奨):
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
say $line;
}
close $fh;
# すべて配列に読み込む:
open my $f2, '<', 'data.txt' or die $!;
my @lines = <$f2>;
close $f2;
# ファイル属性の判定:
if (-e 'data.txt') { say "存在" }
-f 'data.txt'; # 通常ファイルか
-d 'dir'; # ディレクトリか
-r 'data.txt'; # 読み取り可能か
# 特殊文字を含むファイル名は 3 引数 open が安全
# 行単位の処理はまとめ読みより省メモリ

書き込みと追記

> は上書き、>> は追記。print/printf でハンドルに書き込み、umask で権限制御。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 上書き書き込み:
open my $out, '>', 'out.txt' or die $!;
print $out "hello\n";
close $out;
# 追記書き込み:
open my $log, '>>', 'app.log' or die $!;
print $log "time=2026 msg=start\n";
close $log;
# フォーマット書き込み:
printf $out "%05d\n", 42;
# 複数行を一度に書き込む:
open my $f, '>', 'data.txt' or die $!;
print $f @lines;
close $f;
# デフォルト権限 0666 & ~umask:
umask 022;
# 自動生成:> と >> は存在しなければ作成
# バイナリデータは先に binmode

ダイヤモンド演算子

<> は引数ファイル、無ければ STDIN を読み込みます。$. は累計行番号、$ARGV は現在のファイル名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# ダイヤモンド演算子で引数ファイルを読む:
while (<>) {
chomp;
say "读到: $_";
}
# 実行:
# perl script.pl a.txt b.txt
# 引数なしでは STDIN を読む:
# echo hi | perl script.pl
# ハンドルを指定したダイヤモンド:
open my $fh, '<', 'data.txt' or die $!;
while (<$fh>) {
print; # デフォルトで $_ を出力
}
# $. はファイルをまたいで行番号が累積:
while (<>) {
say "第 $. 行: $_";
}
# $ARGV は現在のファイル名:
print "文件: $ARGV\n" if eof;
# 行単位で処理する小ツールに適する

ファイル全体読み込み

local $/ を undef にして一括読み込み。File::Slurper は read_text を提供して手軽です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# ネイティブの slurp:
open my $fh, '<', 'data.txt' or die $!;
local $/; # 区切り文字を undef に
my $content = <$fh>;
close $fh;
# File::Slurper(便利):
use File::Slurper 'read_text';
my $text = read_text('data.txt');
# 一度にファイルを書く:
use File::Slurper 'write_text';
write_text('out.txt', "hello\n");
# バイナリを読む:
# read_binary / write_binary
# 大きなファイルで slurp は避ける:
# メモリを使い切るので,行単位ループで
# エンコーディング処理が必要なら decode と併用

binmode とエンコーディング

binmode でバイナリ安全、<:utf8 層で UTF-8 読み書き、:raw でエンコーディング層を外せます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# バイナリセーフな書き込み:
open my $fh, '>', 'img.bin' or die $!;
binmode $fh;
print $fh pack("C*", 0..255);
close $fh;
# エンコーディング層付きで開く:
open my $in, '<:encoding(UTF-8)', 'data.txt'
or die $!;
# utf8 層の略記:
open my $in2, '<:utf8', 'data.txt' or die $!;
# UTF-8 で出力:
binmode STDOUT, ':utf8';
# エンコーディング層を外す:
binmode $fh, ':raw';
# 読み込んでから変換:
use Encode;
while (my $line = <$in>) {
print encode('UTF-8', $line);
}
# エンコーディング層がバイトレベルの詳細を処理
# 混在エンコーディングのファイルは手動で変換

標準ハンドル

STDIN/STDOUT/STDERR。ハンドルの複製、端末判定、即時フラッシュ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 標準ハンドル:
print STDOUT "标准输出\n";
print STDERR "标准错误\n";
my $line = <STDIN>;
# ハンドルのコピー:
open my $copy, '>&', STDOUT;
print $copy "副本输出\n";
# ターミナルか判定:
my $tty = -t STDOUT;
say $tty ? "终端" : "管道";
# 即時フラッシュ(バッファリングなし):
$| = 1; # print ごとに書き込む
# 固定行数だけ読む:
for (1..3) {
my $l = <STDIN>;
last unless defined $l;
}
# クローズ失敗のチェック:
close $copy or die "关闭失败: $!";
# リダイレクトはシェルが処理:
# perl script.pl > out.txt 2>&1

パス操作

Cwd で現在ディレクトリ、File::Spec でクロスプラットフォームなパス結合、glob でファイルマッチ、File::Find で再帰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
use Cwd;
my $cwd = getcwd();
say $cwd;
use File::Spec;
my $path = File::Spec->catfile('data', 'sub', 'f.txt');
use File::Basename;
my ($name, $dir, $ext) = fileparse(
'data/app.pl', qr/\.[^.]*/);
# 絶対パス:
my $abs = File::Spec->rel2abs('data.txt');
# ファイルのマッチ:
for my $f (glob 'data/*.txt') {
say $f;
}
# 再帰走査:
use File::Find;
find(sub { say $File::Find::name }, '.');
# パス連結は catfile を使い,文字列連結しない

JSON 処理

JSON::PP はコアモジュール:encode/decode、整形、UTF-8。プロダクションは JSON::XS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# JSON::PP はコアモジュール(5.14+):
use JSON::PP;
my $json = JSON::PP->new->utf8->pretty;
# エンコード:
my $data = { name => 'Rex', tags => ['dev'] };
my $str = $json->encode($data);
# デコード:
my $back = $json->decode($str);
say $back->{name};
# 便利な関数:
use JSON::PP qw(encode_json decode_json);
my $s = encode_json({a => 1}); # {"a":1}
my $h = decode_json('{"a":1}');
say $h->{a}; # 1
# デコード失敗の処理:
eval { decode_json("bad json") };
say "解析失败" if $@;
# 性能重視なら JSON::XS
# 構造体は純粋な Perl リファレンスでなければならない

13.よくある落とし穴

Perl の日常開発で踏みがちな落とし穴と正しい書き方。

use strict 忘れ

strict を有効にしない場合、タイプミスは静かに通り抜け、変数は自動でグローバル化されます。strict は安全の底线です。

1
2
3
4
5
6
7
8
9
10
11
# BAD:厳格チェックを有効にしないと,typo が静かに通る
$foo = 1; # $f00 と打ち間違えても気づきにくい
@array = (1, 2); # 宣言なしで自動的にグローバル化
# GOOD:use strict で宣言を強制
use strict;
use warnings;
my $foo = 1;
# typo はコンパイル時にエラーになる
# strict の 3 面:vars refs subs
# 実行時の問題は warnings に任せる

コンテキストの混同

スカラとリストで結果が変わります:配列をスカラコンテキストに置くと長さ。scalar で明示的に。

1
2
3
4
5
6
7
8
9
10
11
12
# BAD:スカラー/リストコンテキストの違いを無視
my @arr = (1, 2, 3);
my $n = @arr; # 3 は配列の長さ
my $s = @arr; # やはり 3
# GOOD:意図を明示
my @arr2 = (1, 2, 3);
my $len = scalar @arr2; # 明示的に長さを求める
my @copy = @arr2; # リストコンテキストでコピー
my $last = $arr2[-1]; # 最後の要素を取る
# リストが欲しいなら (),スカラーが欲しいなら $s
# scalar でスカラーコンテキストを強制

chomp 忘れ

<STDIN> は改行を含むため、chomp を忘れると文字列比較が必ず失敗します。

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:chomp を忘れると,比較は永遠に失敗
my $line = <STDIN>; # 末尾に改行が付く
if ($line eq "quit") { say "不会到这" }
# GOOD:まず chomp してから比較
my $line2 = <STDIN>;
chomp $line2;
if ($line2 eq "quit") { say "退出" }
# chomp は $_ を破壊的に変更し,除去した文字数を返す
# 数値入力もまず chomp を推奨:
my $num = <STDIN>;
chomp $num;
my $value = $num + 0;

比較演算子の混同

== は数値比較、eq は文字列比較。誤って使い分けると "10" と "010" が等しいと判定される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:文字列に == を使うと,数値化後に誤判定
my $a = "10";
my $b = "010";
if ($a == $b) { say "误判相等" } # どちらも 10
# GOOD:文字列は eq
my $a2 = "10";
my $b2 = "010";
if ($a2 eq $b2) { say "不同字符串" }
# 数値比較は ==:
if (10 == 10.0) { say "数值相等" }
# 演算子の対応表:
# == != < > 数値;eq ne lt gt 文字列
# 混在は見つけにくい bug を生む

ハッシュの反復順序は不定

ハッシュの内部順序は不定で、反復順に依存するとランダムにバグります。順序が必要ならキーをソートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BAD:ハッシュの走査順に依存
my %h = (b => 1, a => 2);
for my $k (keys %h) {
print "$k=$h{$k}\n"; # 順序はランダム
}
# GOOD:キーでソートして走査
my %h2 = (b => 1, a => 2);
for my $k (sort keys %h2) {
print "$k=$h2{$k}\n"; # a が先,b が後
}
# ハッシュ内部の順序は不定,依存しない
# 順序を保つなら配列にキーを保存:
my @order = qw(a b c);
for my $k (@order) { print "$k=$h2{$k}\n" }

読み込みで自動有効化が走る

ネスト構造を読み出すと中間リファレンスが自動作成され、メモリが静かに増えます。短絡評価でチェックすれば避けられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BAD:読み取りのネストで意図せず構造が作られる
my $config;
if ($config->{db}{host}) {
say "有配置";
}
# 読み取りで $config->{db} が自動的にリファレンス化される!
# GOOD:短絡評価で空判定
my $config2;
if ($config2 && $config2->{db}{host}) {
say "有配置";
}
# $config2 が undef なら右辺は実行されない
# 余計な構造は作られない
# 書き込みは自動活体化が必要(便利)
# 読み取りは先に defined / exists を判定
# no autovivification で全部無効化できる

local と my の混同

local はグローバル変数を一時的に上書き(ブロック外で復元)、my はレキシカルなローカル変数です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BAD:local をローカル変数として使う
sub work {
local $temp = 5; # 変更しているのはグローバル変数
}
# GOOD:普通の一時変数は my
sub work2 {
my $temp = 5; # レキシカル変数
return $temp;
}
# local は特殊変数を一時的に変える用途:
{
local $/; # 一時的に区切り文字を undef に
my $all = <$fh>; # ファイル全体を一度に読む
}
# local は $! $@ のローカル化にも使う
# local でローカル変数を偽装しない

古い open の書き方

2 引数 open + ベアワードハンドルはシンボルテーブルを汚し安全ではありません。3 引数 + レキシカルハンドルを使いましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:2 引数 open + 裸の句柄
open FH, ">out.txt"; # シンボルテーブルを汚染,モードも危険
# GOOD:3 引数 open + レキシカル句柄
open my $fh, '>', 'out.txt' or die $!;
print $fh "数据\n";
close $fh;
# 読み取りも同様:
open my $in, '<', $user_file or die $!;
# 3 引数はモードとファイル名を区別
# > で始まるファイル名でも誤判定しない:
open my $f, '<', ">weird_name.txt";
# レキシカル句柄は自動クローズ,リークなし

配列のスカラコンテキスト

配列はスカラコンテキストで長さ、リストは最後の要素を返します。長さは scalar で明示。

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:配列をスカラーコンテキストで使うと先頭要素と思い込む
my @arr = (1, 2, 3);
my $x = @arr; # 3(長さ!)
my $y = (1, 2, 3); # 3(リスト末尾の項)
# GOOD:コンテキストを明示
my @arr2 = (1, 2, 3);
my $len = scalar @arr2; # 長さ 3
my ($first, @rest) = @arr2; # 1, (2,3)
my $last = $arr2[-1]; # 3
# カンマ式はスカラーコンテキストで最後の項を取る
# 要素数が欲しいなら scalar で明示
# コピーが欲しいなら @copy = @arr

14.マルチスレッド

threads によるスレッド生成、threads::shared 共有データ、ロックとキューの同期。

スレッド作成

threads->create で新スレッドを起動、join で終了を待ち戻り値を取得します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use threads;
# スレッドを作成:
my @threads;
for my $i (1..3) {
push @threads, threads->create(sub {
say "线程 $i 运行中";
return $i * 10;
});
}
# 待って結果を受け取る:
for my $t (@threads) {
my $result = $t->join();
say "结果: $result";
}
# join はスレッドが終わるまでブロック
# スレッド終了後はオブジェクトが解放される

join とエラー

join はスレッドの結果を返し、スレッド内の die はメインスレッドに伝播します。eval で捕捉可能です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use threads;
# join は戻り値とエラーを受け取る:
my $t = threads->create(sub {
die "线程内错误\n" if rand > 0.5;
return "成功";
});
my $result = $t->join();
if ($result) {
say "返回: $result";
}
# スレッド内の die はメインスレッドも die させる
# join で捕捉したいなら eval を使う:
my $t2 = threads->create(sub {
die "boom\n";
});
eval { $t2->join() };
say "捕获: $@" if $@;
# join されないスレッドはリークする
# detach でスレッド終了時に自己クリーンアップ

共有データ

threads::shared の :shared 変数はスレッド間で共有。通常の my 変数はスレッドごとのコピーです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
use threads;
use threads::shared;
# 共有変数の宣言:
my $count :shared = 0;
my @list :shared = ();
my %seen :shared = ();
# 共有ハッシュのネストしたキー値も共有が必要:
my %stats :shared;
# 複数スレッドの同時 ++ はアトミック:
for (1..5) {
threads->create(sub { $count++ })->join();
}
say "count = $count"; # 5
# 共有配列/ハッシュだけがスレッド間で読み書きできる
# 通常の my 変数はスレッドごとに独立したコピー

ロックとセマフォ

lock はクリティカルセクションを保護、Thread::Semaphore は流量制限。ロックはスコープ終了で自動解放されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
my $counter :shared = 0;
my $lock :shared;
# lock でクリティカルセクションを保護:
sub increment {
lock $lock; # 他のスレッドは待つ
$counter++;
# スコープ終了で自動解放
}
# セマフォで流量制限:
my $sem = Thread::Semaphore->new(2);
sub worker {
$sem->down(); # 許可を 1 つ取得
do_work();
$sem->up(); # 解放
}
# lock はブロック終了で自動解放
# デッドロックのリスク:ネストする lock は順序を揃える

スレッドキュー

Thread::Queue はスレッドセーフなキューで、空のとき dequeue はブロックします。 Producer-Consumer パターンに適します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use threads;
use Thread::Queue;
# スレッドセーフなキュー:
my $q = Thread::Queue->new();
# プロデューサー:
threads->create(sub {
$q->enqueue($_) for 1..5;
$q->end(); # 終了をマーク
});
# コンシューマー:
my $worker = threads->create(sub {
while (defined(my $item = $q->dequeue())) {
say "处理: $item";
}
});
$worker->join();
# dequeue はキューが空だとブロック
# enqueue は一括対応:
# $q->enqueue(@items);
# プロデューサー・コンシューマーモデルに最適

スレッド引数

create の第 2 引数以降はサブルーチンへの引数です。レキシカル変数はスレッドごとに独立コピー。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use threads;
# スレッドは引数とレキシカル変数をコピーする:
my $base = 10;
my $t = threads->create(sub {
my $num = shift;
return $num + $base; # $base はコピー
}, 5);
say $t->join(); # 15
# create のたびに引数を渡す:
my @results = map {
threads->create(sub {
my $x = shift;
return $x * $x;
}, $_)->join();
} 1..3;
# レキシカル変数はスレッドごとに 1 コピー
# 共有が必要なら必ず :shared を使う

並列タスク

固定数のスレッドでタスクを処理、共有インデックスを原子的に割り当て、タスクごとにスレッドを立てる無駄を避けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use threads;
use threads::shared;
# シンプルな並列タスクプール:
my @jobs = 1..10;
my @workers;
my $total :shared = 0;
my $next :shared = 0;
# 固定 4 つのワーカースレッド:
for (1..4) {
push @workers, threads->create(sub {
while (1) {
my $job;
lock $next;
last if $next >= @jobs;
$job = $jobs[$next++];
# 時間のかかる仕事:
$total += $job;
}
});
}
$_->join() for @workers;
say "总和: $total";
# タスクを固定スレッドプールに振り分け
# タスクごとにスレッドを新規作成するオーバーヘッドを回避

スレッドプールパターン

キューでディスパッチ + 結果キューで回収。end を複数回呼ぶことでブロック中の全スレッドを起こすことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use threads;
use Thread::Queue;
# 固定スレッドプールパターン:
my $q = Thread::Queue->new();
my $result_q = Thread::Queue->new();
# コンシューマースレッドプール:
my @workers = map {
threads->create(sub {
while (defined(my $job = $q->dequeue())) {
my $out = process($job);
$result_q->enqueue($out);
}
})
} 1..4;
# タスクを配布:
$q->enqueue($_) for 1..100;
$q->end() for 1..4; # 全スレッドを終了させる
# 結果を収集:
my @results;
while (my $r = $result_q->dequeue()) {
push @results, $r;
}
$_->join() for @workers;
# スレッドプールはスレッドを再利用し,オーバーヘッド最小
# 結果キューで共有構造への直接書き込みを避ける

15.ネットワークプログラミング

TCP ソケット、HTTP クライアント、URL 解析、DNS クエリ。

TCP クライアント

IO::Socket::INET で接続を作成し、print で送信、<...> で応答を読み込みます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use IO::Socket::INET;
# TCP クライアント:
my $sock = IO::Socket::INET->new(
PeerHost => 'example.com',
PeerPort => 80,
Proto => 'tcp',
Timeout => 10,
) or die "无法连接: $@";
# HTTP リクエストを送る:
print $sock "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
# レスポンスを読む:
while (my $line = <$sock>) {
print $line;
}
close $sock;
# 作成失敗時は $@ に原因が入る
# Timeout で無限ブロックを防ぐ

TCP サーバ

Listen でポートを待ち受け、accept で接続を受け、各接続をエコーまたは子プロセスで処理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use IO::Socket::INET;
# TCP サーバー:
my $server = IO::Socket::INET->new(
LocalAddr => '0.0.0.0',
LocalPort => 8080,
Proto => 'tcp',
Listen => 10,
ReuseAddr => 1,
) or die "无法监听: $@";
say "监听 8080 端口";
while (my $client = $server->accept()) {
# 各接続で単純にエコー:
my $line = <$client>;
print $client "收到: $line";
close $client;
}
# 多数の接続は fork と組み合わせて処理
# ReuseAddr で再起動時のポート占有を回避

HTTP GET

HTTP::Tiny はコアモジュール。get は status/content を返し、JSON::PP と組み合わせて解析できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use HTTP::Tiny;
my $ua = HTTP::Tiny->new(
timeout => 10,
agent => 'Mozilla/5.0',
);
# GET リクエスト:
my $res = $ua->get('https://api.example.com/data');
if ($res->{success}) {
say "状态: $res->{status}";
print $res->{content};
} else {
warn "请求失败: $res->{status}";
}
# JSON レスポンスを解析:
use JSON::PP;
my $data = decode_json($res->{content});
say $data->{name};
# HTTP::Tiny はコアモジュール
# リダイレクト/プロキシはオプションで設定可能

HTTP POST

post は任意のボディを送信、post_form は自動で URL エンコードしてフォームを送信します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $ua = HTTP::Tiny->new();
# POST JSON:
my $payload = encode_json({
name => 'Rex',
age => 5,
});
my $res = $ua->post(
'https://api.example.com/users',
{
content => $payload,
headers => {
'Content-Type' => 'application/json',
},
},
);
# フォームエンコード POST:
my $form = $ua->post_form(
'https://example.com/login',
{ user => 'admin', pass => 'secret' },
);
# 結果を確認:
if ($res->{success}) {
say "已提交";
}
# post_form は自動で URL エンコード
# ファイル送信は multipart かファイルハンドルを使う

URL 解析

URI モジュールが URL の分解と構築を行います。query_form でクエリパラメータの読み書きと自動エンコードが可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use URI;
# URL を解析:
my $uri = URI->new('https://user:[email protected]:8080/path?a=1&b=2#frag');
say $uri->scheme; # https
say $uri->host; # example.com
say $uri->port; # 8080
say $uri->path; # /path
# クエリパラメータ:
my %query = $uri->query_form;
say $query{a}; # 1
# URL を構築:
my $u = URI->new('https://example.com/api');
$u->query_form(name => 'Rex', page => 2);
print $u; # エンコード済みのクエリ文字列
# 相対パスの結合:
my $abs = URI->new_abs('about.html', 'https://site.com/docs/');
# URI モジュールがエンコード/エスケープを処理
# URL を手組みせず,URI で正しさを保証

DNS クエリ

Net::DNS は A/MX/TXT レコードを解決します。search はレスポンスオブジェクトを返し、外部モジュールが必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use Net::DNS;
# A レコードを問い合わせ:
my $resolver = Net::DNS::Resolver->new();
my $query = $resolver->search('example.com');
if ($query) {
for my $rr ($query->answer) {
if ($rr->type eq 'A') {
say $rr->address;
}
}
} else {
warn "查询失败: ", $resolver->errorstring;
}
# 他のレコードタイプ:
# $resolver->search('example.com', 'MX');
# $resolver->search('example.com', 'TXT');
# またはサーバーを手動指定:
Net::DNS::Resolver->new(nameservers => ['8.8.8.8']);
# Net::DNS モジュールが必要(コアではない)

ソケットオプション

setsockopt で低レベルオプションを設定、timeout でブロックを制御、IO::Select で非ブロッキング待機。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use IO::Socket::INET;
my $sock = IO::Socket::INET->new(
PeerHost => 'example.com',
PeerPort => 443,
Proto => 'tcp',
Timeout => 5,
) or die "连接失败";
# オプションを設定:
$sock->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1);
# タイムアウト:
$sock->timeout(10);
# 読み書き:
print $sock "ping\n";
my $reply = <$sock>;
# 非ブロッキング読み取り:
use IO::Select;
my $sel = IO::Select->new($sock);
if ($sel->can_read(2)) {
my $data = <$sock>;
}
# SO_RCVBUF などのバッファオプションを調整可能
# IO::Select で複数ハンドルの待機を実現

REST クライアント

HTTP::Tiny をラップして認証と JSON を統一処理し、エラーは呼び出し元に一様に投げます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $base = 'https://api.example.com/v1';
my $ua = HTTP::Tiny->new(timeout => 10);
# 汎用 REST ラッパー:
sub api {
my ($method, $path, $body) = @_;
my $url = $base . $path;
my %opts = (headers => {
'Accept' => 'application/json',
'Authorization' => 'Bearer token123',
});
$opts{content} = encode_json($body) if $body;
my $res = $ua->request($method, $url, \%opts);
return decode_json($res->{content})
if $res->{success};
die "API 错误: $res->{status}";
}
my $user = api('GET', '/users/1');
my $created = api('POST', '/users',
{ name => 'Rex', age => 5 });
# エラーは一括して呼び出し元へ
# ページネーション/リトライは必要に応じて強化

16.日付と時刻

time/localtime、strftime フォーマット、Time::Piece、DateTime の処理。

localtime 分解

time は epoch を返し、localtime は年月日时分秒に分解します。月と年のオフセットに注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
# localtime は時刻の各要素を返す:
my ($sec, $min, $hour, $mday, $mon, $year,
$wday, $yday, $isdst) = localtime;
# 月は 0 始まり,年はオフセット:
my $display = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday,
$hour, $min, $sec;
say $display;
# gmtime は UTC を返す:
my @utc = gmtime;
# time は現在の epoch を返す:
my $now = time;
say "当前时间戳: $now";
# localtime はシステムのタイムゾーンに依存
# フォーマットは strftime の方が明確

strftime フォーマット

POSIX::strftime は %Y %m %d などのプレースホルダでフォーマット、%A %B は曜日と月名を出力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use POSIX 'strftime';
# strftime で時刻をフォーマット:
my $now = time;
my $fmt = strftime(
'%Y-%m-%d %H:%M:%S', localtime $now);
say $fmt; # 2026-08-02 12:34:56
# よく使う書式:
# %Y 4 桁の年 %y 2 桁
# %m 月 %d 日 %H 時 %M 分 %S 秒
# %A 曜日のフルネーム %a 省略形
# %B 月のフルネーム %b 省略形
# %j 年の通算日
my $date = strftime('%A %B %d', localtime);
my $custom = strftime('%Y/%m/%d %I:%M %p',
localtime); # 12 時間制
# strftime は C 関数で,ロケールに応じて出力

タイムスタンプ変換

timelocal/timegm はローカル/UTC の時間配列を epoch に戻します。オフセットに注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use Time::Local;
# time は現在の epoch 秒を返す:
my $now = time;
# epoch を配列に変換(ローカル/UTC):
my @local = localtime $now;
my @utc = gmtime $now;
# 配列を epoch に変換:
use Time::Local 'timelocal';
my $epoch = timelocal(
$sec, $min, $hour, $mday, $mon, $year);
# 日付文字列から epoch を解析:
my $ts = timelocal(0, 0, 12, 2, 7, 126);
say "2026-08-02 12:00 的 epoch: $ts";
# timegm は UTC を処理:
use Time::Local 'timegm';
my $utc_epoch = timegm(0, 0, 0, 1, 0, 126);
# 月と年のオフセットに注意(月 -1,年 -1900)

sleep による待機

sleep は整数秒。Time::HiRes は小数やマイクロ秒をサポート、select で非ブロッキング待機が可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# sleep は指定秒数ブロック:
sleep 2;
say "睡醒了";
# 小数対応(Time::HiRes が必要):
use Time::HiRes qw(sleep usleep);
sleep 0.5; # 半秒
usleep 500000; # マイクロ秒
# 定時ループ:
for (1..3) {
say "第 $_ 次";
sleep 1;
}
# 非ブロッキングな遅延方法:
# select の空タイムアウトを使う:
use IO::Select;
my $sel = IO::Select->new();
$sel->can_read(0.5); # 待つが読まない
# sleep はシグナル割り込み後,残り秒数を返す
# 本番のスケジューリングは Time::HiRes の方が正確

経過時間の測定

time は秒精度、Time::HiRes はミリ秒精度。日付やタイムゾーンをまたぐ差分は DateTime を使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 簡単な計時(秒):
my $t0 = time;
do_work();
my $elapsed = time - $t0;
say "耗时 $elapsed 秒";
# 高精度計時:
use Time::HiRes qw(time);
my $s = time;
do_work();
my $ms = (time - $s) * 1000;
say "耗时 $ms 毫秒";
# タイムゾーン/日付の差は DateTime:
use DateTime;
my $a = DateTime->now;
my $b = $a->clone->add(days => 3);
my $dur = $b - $a;
say $dur->in_units('hours'); # 72
# DateTime は期間オブジェクトを返す
# 夏時間の処理は DateTime を優先

DateTime オブジェクト

DateTime はフル機能な時間処理:構築、取得、タイムゾーン変換、日付演算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use DateTime;
# 構築と値の取得:
my $dt = DateTime->now;
my $d2 = DateTime->new(
year => 2026, month => 8, day => 2,
hour => 12, minute => 30,
);
say $d2->ymd; # 2026-08-02
say $d2->hms; # 12:30:00
say $d2->year;
# タイムゾーン:
my $tokyo = DateTime->now(time_zone => 'Asia/Tokyo');
my $utc = $tokyo->set_time_zone('UTC');
# 算術:
my $later = $dt->clone->add(days => 7, hours => 3);
my $earlier = $dt->clone->subtract(months => 1);
# フォーマット:
say $dt->format_cldr('yyyy-MM-dd HH:mm');
# DateTime は外部モジュールで,機能が豊富
# 夏時間/タイムゾーン変換の処理が安全

文字列解析

Time::Piece::strptime は書式で日付文字列を解析し、epoch と差分を取得できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use Time::Piece;
# Time::Piece は strptime 解析を内蔵:
my $t = Time::Piece->strptime(
'2026-08-02 12:30:00', '%Y-%m-%d %H:%M:%S');
say $t->epoch;
# 他の形式を解析:
my $d = Time::Piece->strptime(
'02/Aug/2026', '%d/%b/%Y');
# フォーマット出力:
say $t->strftime('%Y/%m/%d');
# epoch から構築:
my $now = localtime; # Time::Piece オブジェクト
say $now->epoch;
say $now->year; # 2026
# 差分:
my $diff = $t - $now;
say $diff->days;
# Time::Piece はコアモジュール
# strptime は失敗すると例外を投げる

フォーマットオブジェクト

DateTime::Format::Strptime は解析とフォーマットを統一。RFC3339 で ISO 時刻を扱います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use DateTime;
# DateTime のフォーマットと解析:
use DateTime::Format::Strptime;
my $format = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M:%S',
time_zone => 'UTC',
);
# 文字列を解析:
my $dt = $format->parse_datetime(
'2026-08-02 12:00:00');
# フォーマット出力:
my $str = $format->format_datetime($dt);
say $str;
# よく使う pattern:
# %Y 年 %m 月 %d 日
# %H 時 %M 分 %S 秒 %z タイムゾーンオフセット
my $iso = $dt->format_cldr('yyyy-MM-dd HH:mm:ss');
# より簡潔な RFC3339:
use DateTime::Format::RFC3339;
my $rfc = DateTime::Format::RFC3339->parse_datetime(
'2026-08-02T12:00:00Z');
# フォーマットオブジェクトで解析/フォーマットを統一

17.プロセスとコマンド

system/バッククォートでのコマンド実行、環境変数、@ARGV、シグナル、パイプ。

コマンド実行

バッククォートは出力を取り込む。system は取り込まずに実行する。リスト形式ならシェルインジェクションを防げる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
# バッククォートで出力を捕捉:
my $out = `ls -la`;
print $out;
# qx の方が明確(区切り文字をカスタム可能):
my $date = qx(date);
chomp $date;
# system は実行するが捕捉しない:
system("echo hello");
# system は終了ステータスを返す
# バッククォートはスカラーコンテキストで出力全体を返す
# リストコンテキストでは行ごとに分割:
my @lines = `cat file.txt`;
# エスケープとインジェクションに注意:
# リスト形式で shell の連結を避ける:
system("ls", "-l");
# ユーザー入力をコマンド文字列に連結しない

exec と system

exec は現在のプロセスを置き換え、system は子プロセスの終了を待ち、$? は終了ステータスを保存します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
# exec は現在のプロセスを置き換える:
# exec "ls", "-l"; # この後のコードは実行されない
# system は子プロセスの終了を待つ:
system("echo", "hi");
say "system 之后";
# 戻り値は終了コード:
my $status = system("true");
say "退出码: $status"; # 0
# $? に最後の状態が入る:
my $exit = $? >> 8; # 実際の終了コード
my $sig = $? & 127; # シグナル
# exec も system も出力を捕捉しない:
# 出力が必要ならバッククォートか open パイプ
# exec は別のプログラムに置き換える用途

環境変数

%ENV ハッシュで環境変数の読み書き、子プロセスにも影響。local で一時的に隔離可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 環境変数を読み取る:
my $home = $ENV{HOME};
say "主目录: $home";
# 環境変数を設定:
$ENV{PATH} = '/usr/bin:/bin';
$ENV{LANG} = 'en_US.UTF-8';
# すべての環境変数を走査:
for my $key (sort keys %ENV) {
print "$key=$ENV{$key}\n";
}
# 子プロセスに渡す:
$ENV{MODE} = 'test';
system("echo", $ENV{MODE});
# 環境変数を削除:
delete $ENV{SECRET};
# %ENV は以降のすべての子プロセスに影響
# スクリプト環境の分離は local:
{
local $ENV{DEBUG} = 1;
run_tests();
} # ブロック終了で復元

コマンドライン引数

@ARGV が引数リスト。複雑な解析は Getopt::Long を宣言的に使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# @ARGV はコマンドライン引数:
my ($file, $count) = @ARGV;
say "文件: $file, 次数: $count";
# 引数はそのまま配列:
my @files = @ARGV;
for my $f (@files) {
say "处理: $f";
}
# shift はデフォルトで @ARGV から取る:
my $first = shift @ARGV;
# 引数がないときはデフォルトで STDIN を読む:
# while (<>) { ... }
# 引数解析は Getopt::Long を推奨:
use Getopt::Long;
my ($verbose, $name);
GetOptions(
'verbose' => \$verbose,
'name=s' => \$name,
);
say $verbose ? "详细模式" : "简洁模式";
say "名字: $name" if $name;
# $0 はスクリプト名

終了ステータス

exit N で終了コードを設定。0 が成功、非 0 が失敗。END ブロックは終了前に実行されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
# exit は終了してステータスコードを設定:
exit 0; # 成功
# exit 1; # 失敗
# 引数なしの終了コードは 0
# 条件付き終了:
my $ok = check();
exit 1 unless $ok;
# die の終了コードは 255(または $!):
# 失敗シナリオでも:
if ($err) {
print STDERR "失败: $err\n";
exit 2;
}
# $? と system:
system("false");
say $?; # 256(128 + 終了コード)
my $code = $? >> 8; # 1
# shell とやりとりする際の約束:
# 0 成功;非 0 失敗
# END ブロックは終了前に実行:
END { print "清理\n" }
# シグナル割り込みのときは $? で原因を判断

シグナル処理

$SIG{INT} でシグナルを捕捉。ハンドラではフラグ設定のみにしてメインループが応答します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
# シグナルを捕捉:
$SIG{INT} = sub {
print "收到中断,清理退出\n";
exit 1;
};
$SIG{TERM} = 'DEFAULT'; # デフォルトに戻す
$SIG{QUIT} = 'IGNORE'; # 無視
# 待機ループ:
while (1) { sleep 1 }
# シグナルを送る:
kill 'TERM', $pid;
# 子プロセスへシグナル:
my $pid = fork();
if ($pid == 0) {
exit 0; # 子プロセス
}
kill 'USR1', $pid;
# シグナルハンドラで重い処理はしない:
# フラグを立てるだけにして,メインループで処理:
my $stop = 0;
$SIG{INT} = sub { $stop = 1 };
while (!$stop) { work() }
# %SIG はグローバルハッシュ
# Windows ではシグナル対応が限られる

プロセス間パイプ

-| は子プロセスの出力を読み、|- は子プロセスの入力に書き込み。双方向は IPC::Open3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# パイプでコマンド出力を読む:
open my $fh, '-|', 'grep error /var/log/app.log'
or die "无法执行: $!";
while (my $line = <$fh>) {
chomp $line;
say "匹配: $line";
}
close $fh;
# パイプに書き込む:
open my $out, '|-', 'gzip > out.gz'
or die "无法执行: $!";
print $out "要压缩的数据\n";
close $out;
# 別の書き方 open2/open3:
use IPC::Open3;
my $pid = open3(
my $in, my $err, 'cmd', 'args');
# '-|' は子プロセスの stdout を読む
# '|-' は子プロセスの stdin に書く
# 双方向のやりとりは IPC::Open3

出力の捕捉

リダイレクトで stdout/stderr を統合、$? で終了コード確認、Capture::Tiny で構造化捕捉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# stdout + stderr を捕捉:
my $out = `cmd 2>&1`;
# stderr のみ捕捉:
my $err = `cmd 2>&1 1>/dev/null`;
# 終了コードを確認(バッククォート):
my $result = `grep pattern file.txt`;
my $exit = $? >> 8;
if ($exit == 0) {
say "找到匹配";
} else {
say "无匹配或出错";
}
# 捕捉時にタイムアウト防止:
# 複雑な場面は IPC::Open2 か IPC::Run
# 安全に実行して出力を取る:
use Capture::Tiny qw(capture);
my ($stdout, $stderr, $exit) =
capture { system("ls", "-l") };
say $stdout;
# Capture::Tiny は外部モジュール
# 大量のコマンド連携は IPC::Run を検討

18.正規表現

パターンマッチ、文字クラス、量指定子、キャプチャグループ、事前コンパイル、置換。

マッチの基礎

=~ はマッチ、!~ は非マッチ。アンカー ^ $ \b で位置指定、i 修飾子で大文字小文字を無視。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# マッチ演算子 =~ m//:
my $str = "hello world";
if ($str =~ /world/) {
say "包含 world";
}
# 非マッチ !~:
if ($str !~ /xyz/) {
say "不含 xyz";
}
# アンカー:
/^start/; # 行頭
/end$/; # 行末
/\bword\b/; # 単語境界
# 大文字小文字を無視:
/hello/i;
# 一度マッチして結果を取る:
my ($match) = $str =~ /(\w+) (\w+)/;
# 他の修飾子は以降の各節を参照

マッチと抽出

g でグローバルマッチ、リストコンテキストで全ヒットを返し、grep と組み合わせて配列をフィルタリング。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# マッチするか判定:
if ($line =~ /error/) { ... }
# リストコンテキストで全ヒットを取得:
my $str = "x1y2z3";
my @nums = $str =~ /(\d)/g; # 1 2 3
# マッチ回数を数える:
my $count = () = $str =~ /\d/g;
# grep フィルタと組み合わせ:
my @words = qw(cat dog bird);
my @short = grep { length $_ < 4 } @words;
# 変数補間でパターンを構築:
my $pat = "a.c";
$str =~ /$pat/; # . は任意の文字
# プリコンパイルは後続のトピック:
my $re = qr/\d{3}-/;
$str =~ $re;
# grep/map は $_ で暗黙的に反復

文字クラス

\d \w \s とその否定、カスタム [...]、否定 [^...]、量指定子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
# よく使う文字クラス:
# \d 数字 [0-9]
# \w 単語文字 [A-Za-z0-9_]
# \s 空白 [ \t\n\r\f]
# 大文字は否定:\D \W \S
# カスタム文字クラス:
/[aeiou]/; # 母音
/[0-9a-fA-F]/; # 16 進数
/[^0-9]/; # 数字以外
# 量指定子:
/a*/; # 0 個以上
/a+/; # 1 個以上
/a?/; # 0 または 1 個
/a{3}/; # ちょうど 3 個
/a{2,4}/; # 2 から 4 個
/a{2,}/; # 少なくとも 2 個
# グループ化と択一:
/(foo|bar)/;
/(ab)+/;
# 特殊文字のエスケープ:
/\$dollar/;
/1\+1/;
# ドット . はデフォルトで任意の文字(改行以外)

量指定子とバックトラック

デフォルトは貪欲、? 付加で非貪欲。占有量指定子 + でバックトラック禁止、ネストした量指定子は catastrophic backtracking に注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
# 貪欲 vs 非貪欲:
my $s = "<b>hi</b><b>yo</b>";
my @greedy = $s =~ /<b>.*<\/b>/g; # 文字列全体
my @lazy = $s =~ /<b>.*?<\/b>/g; # 2 つの小さな塊
# 量指定子:
# * 貪欲 0+ *? 非貪欲
# + 貪欲 1+ +? 非貪欲
# ? 0/1 ?? 非貪欲
# {n,m} {n,m}? 非貪欲
# 占有量指定子(バックトラック禁止):
my $big = "aaaaa";
$big =~ /a++a/; # 失敗(a++ が占有)
$big =~ /a+a/; # 成功(バックトラック)
# 行アンカー修飾:
/^foo/m; # m で ^$ が各行にマッチ
/foo./s; # s で . が改行にもマッチ
# 性能注意:ネストした量指定子は壊滅的なバックトラックになりやすい
# 単純な目標は固定文字クラスで代替

キャプチャグループ

$1 $2 は左括弧順で取得。(?:) は非キャプチャ、(?<name>) は名前付きキャプチャ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# キャプチャグループ $1 $2:
my $s = "name=alice age=30";
if ($s =~ /name=(\w+) age=(\d+)/) {
say "名字: $1, 年龄: $2";
}
# リストコンテキストで全キャプチャを取得:
my ($name, $age) = $s =~ /name=(\w+) age=(\d+)/;
# ネストしたキャプチャ:
if ("abc123" =~ /((\w+)(\d+))/) {
say $1; # abc123
say $2; # abc
say $3; # 123
}
# 非キャプチャグループ:
"hello" =~ /(?:he)(llo)/; # $1 は llo
# 名前付きキャプチャ:
if ("Rex 5" =~ /(?<who>\w+) (?<num>\d+)/) {
say $+{who}; # Rex
say $+{num}; # 5
}
# $1-$9 は 1 始まり,未マッチは undef
# キャプチャグループの順は左括弧の出現順

名前付きキャプチャ

(?<name>...) 名前付きグループ + %+ ハッシュアクセス、\k<name> で後方参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 名前付きキャプチャグループ:
my $line = "2026-08-02 12:30";
if ($line =~ /(?<year>\d{4})-(?<mon>\d{2})-(?<day>\d{2}) (?<time>\d{2}:\d{2})/) {
say "年: $+{year}";
say "月: $+{mon}";
say "日: $+{day}";
say "时间: $+{time}";
}
# %+ ハッシュでアクセス:
say $+{year};
# 後方参照:
/(?<pair>\w)\k<pair>/; # 同じ文字の繰り返し
# 数字の後方参照:
/(\w)\1/;
# 分岐リセット:
"ab" =~ /(?|a(b)|c(d))/; # どちらの分岐でも $1
# 名前付きキャプチャで可読性が大幅に向上
# %- は同じ名前のキャプチャをすべて保存

正規表現の事前コンパイル

qr// で一度コンパイルして再利用、モジュール内で共有すれば大きなループの性能向上。修飾子も指定可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# プリコンパイル正規表現:
my $email_re = qr/[\w.]+@[\w.]+\.\w+/;
my $phone_re = qr/1[3-9]\d{9}/;
# マッチの再利用:
my $text = "联系 a@b.com 或 13800138000";
my @emails = $text =~ /($email_re)/g;
my @phones = $text =~ /($phone_re)/g;
say "邮箱: @emails";
# より大きな正規表現に補間:
my $full = qr/($email_re|$phone_re)/;
# モジュール内で共有(1 回だけコンパイル):
{
my $DATE_RE = qr/\d{4}-\d{2}-\d{2}/;
sub is_date { $_[0] =~ $DATE_RE }
}
# プリコンパイルで毎回の再コンパイルを回避
# 大きなループでは性能向上が顕著
# qr は修飾子を付けられる:
my $ci = qr/foo/i;

置換と文字変換

s/// で置換、g でグローバル、i で大小文字無視。置換部に $1 が使えます。tr/// は文字置換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# s/// 置換:
my $s = "apple apple";
$s =~ s/apple/orange/; # 最初の 1 つだけ置換
say $s; # orange apple
# g でグローバル置換:
$s =~ s/apple/kiwi/g;
say $s; # kiwi kiwi
# i で大文字小文字を無視:
"HELLO" =~ s/hello/hi/i; # hi
# 置換にキャプチャを使う:
my $date = "2026-08-02";
$date =~ s/(\d{4})-(\d{2})-(\d{2})/$2\/$3\/$1/;
# 置換部分では $1 $2 を使う
# 空置換で数を数える:
my $count = ($s =~ s/x//g);
# tr/// は文字単位の変換(正規表現なし):
my $t = "hello";
$t =~ tr/a-z/A-Z/; # HELLO
# s は置換回数を返す(スカラーコンテキスト)
# 置換部分のバックスラッシュのエスケープに注意

19.モジュールとプロジェクト構成

モジュール構造、use/require、オプション解析、CPAN インストール、テスト。

モジュール構造

パッケージ名はファイルパスに対応。Exporter でエクスポート、末尾の 1; で use 成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package My::Greet;
use strict;
use warnings;
# エクスポートリスト:
use Exporter 'import';
our @EXPORT_OK = qw(greet);
our @EXPORT = qw(); # デフォルトではエクスポートしない
sub greet {
my $name = shift;
return "hello, $name";
}
# バージョン番号:
our $VERSION = '0.01';
1; # モジュールは真を返さなければならない
# ファイル名はパッケージ名に対応:
# My/Greet.pm → My::Greet
# @EXPORT は自動エクスポート;@EXPORT_OK は明示要求が必要

use と require

use はコンパイル時にロード&インポート、require はランタイムロード、do はファイル実行(シンボルテーブルなし)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# use はコンパイル時に読み込み + インポート:
use List::Util 'sum';
say sum(1, 2, 3);
# 読み込むだけでインポートしない:
use List::Util ();
# require は実行時に読み込み:
require JSON::PP;
say JSON::PP::encode_json({a => 1});
# do はファイルを実行(シンボルテーブルなし):
my $cfg = do './config.pl';
# 読み込み済みか確認:
my $loaded = $INC{'List/Util.pm'};
# 読み込み失敗:
# use はコンパイル時の失敗で終了する
# require は失敗で例外を投げるので捕捉可能:
eval { require Missing::Module };
say "加载失败: $@" if $@;

モジュールインストール

cpanm で CPAN モジュールをインストール、コアモジュールは Perl 同梱、cpanfile で依存を記録。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# インストール済みモジュールを探す:
# perldoc -l Module
# モジュール情報を確認:
# perldoc Module::Name
# cpanm でインストール:
# cpanm DateTime
# バージョン指定:
# スクリプト内で可用性を確認:
use List::Util; # コアモジュールは Perl に同梱
# コア vs CPAN の区別:
# コア:Perl と一緒に配布,インストール不要
# CPAN:自分でインストールが必要(例 Moose)
# 可搬性のためコアモジュールへの依存を推奨:
use JSON::PP; # コア
use HTTP::Tiny; # コア
use Time::Piece; # コア
# コアモジュール一覧を見る:
# corelist -a Time::Piece
# プロジェクト依存は cpanfile に記録

Getopt::Long

宣言的なオプション解析:=s 文字列、=i 整数、フラグ、エイリアス、否定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use Getopt::Long;
# オプションを宣言:
my ($verbose, $name, $count, $help);
GetOptions(
'verbose|v' => \$verbose,
'name=s' => \$name,
'count=i' => \$count,
'help|h' => \$help,
) or die "参数错误";
# 使用:
if ($help) { print_usage(); exit 0 }
say $verbose ? "详细模式" : "简洁模式";
say "名字: $name" if defined $name;
say "次数: $count" if defined $count;
# よく使う型:
# =s 文字列 =i 整数 =f 浮動小数点
# =s@ 複数値 ! 否定スイッチ
# 長いオプションは --name=value と --name value
# 短いオプションの併記:-vh は -v -h と等価
# GetOptions は省略形をサポート(曖昧でないとき)

よく使う CPAN モジュール

DateTime/Moose/DBI などのエコシステム、MetaCPAN で検索、eval で利用可否を判定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# よく使う CPAN モジュール(インストール必要):
# DateTime 日時処理
# Moose / Moo オブジェクトシステム
# DBI データベースアクセス
# Mojo::UserAgent Web クライアント
# JSON::XS 高速 JSON
# LWP::UserAgent 定番 HTTP クライアント
# Text::CSV CSV 解析
# XML::LibXML XML 解析
# インストール方法:
# cpanm Mojo::UserAgent
# またはスクリプト内で検出:
my $has_json = eval { require JSON::XS; 1 };
# 使用例(Mojolicious):
# use Mojo::UserAgent;
# my $ua = Mojo::UserAgent->new;
# my $tx = $ua->get('https://example.com');
# say $tx->result->body;
# インストール済みモジュールを確認:
# perldoc perlmodlib
# モジュール検索は MetaCPAN:
# https://metacpan.org

スクリプトとコマンドライン

shebang でインタプリタを指定、perl -w/-e/-ne はよく使う CLI オプション。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
# 実行可能スクリプト:
# chmod +x script.pl
# ./script.pl を直接実行
# 可搬性のある shebang(二択):
# #!/usr/bin/env perl
# PATH 内の perl を使う(仮想環境に親和)
# コマンドライン実行:
# perl script.pl
# perl -w script.pl # 警告を有効化
# perl -Mstrict script.pl # strict を有効化
# perl -e 'print "hi"' # 一行実行
# perl -ne 'print if /foo/' file # 行ごとに処理
# 引数のチェック:
my ($input, $output) = @ARGV;
die "用法: $0 <in> <out>\n" unless $input && $output;
say "处理 $input$output";
# $0 はスクリプト名;@ARGV は引数リスト
# スクリプト先頭は必ず use strict + warnings

パッケージングとディレクトリ

lib/t ディレクトリ構成、Makefile.PL ビルド、-Ilib でローカル開発ロード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package My::Lib;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT_OK = qw(helper);
our $VERSION = '0.01';
sub helper { return 42 }
1;
# ディレクトリ構成:
# lib/My/Lib.pm モジュール本体
# t/01-basic.t テスト
# Makefile.PL / cpanfile ビルド手順
# Changes / README 変更履歴とドキュメント
# ローカル開発での読み込み:
# -I でライブラリディレクトリを指定:
# perl -Ilib t/01-basic.t
# または PERL5LIB=lib
# ビルドとテスト(定番フロー):
# perl Makefile.PL
# make
# make test
# モジュールのパッケージングと公開:
# cpanm はローカルディレクトリからインストール可能
# バージョン番号を変えたら Changes も同期

Test::More

plan でアサーション件数を宣言、is/ok/like でアサート、prove -l で一括実行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use Test::More;
# テスト数を宣言:
plan tests => 4;
# アサーション:
is(add(2, 3), 5, "2+3=5");
ok(1 == 1, "真值断言");
isnt("a", "b", "不等");
like("hello", qr/^he/, "匹配断言");
sub add { $_[0] + $_[1] }
# テストファイルは単体で実行:
# perl t/01-basic.t
# または prove と併用:
# prove -l t/
# 他のアサーション:
# is_deeply は複雑な構造を比較
# cmp_ok はカスタム比較
# done_testing は数を自動集計
# 条件付きテストをスキップ:
# SKIP: { skip "Windows 不支持", 1 }
# Test::More はコアモジュール

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは Perl 5.38 の自己完結型チートシートです。言語コアと最もよく使う組み込み関数(および一部の CPAN モジュール)を、実プロジェクトの約 80% の典型的な使い方をカバーする形でまとめています。内容はモダンなイディオム重視:`use strict` + `use warnings` を必須とし、後置修飾の制御フロー、scalar/list コンテキストの区別、リファレンスによる複雑なデータ構造、`say` / `state` / `signatures`。Perl は 1987 年に Larry Wall が公開したマルチパラダイムの動的言語で、TMTOWTDI(There's More Than One Way To Do It:やり方はひとつではない)で知られ、テキスト処理、システム管理、Web バックエンドに強みがあります。権威あるリファレンスは Perl 同梱の `perldoc` と [Perl 公式ドキュメント](https://perldoc.perl.org/) を参照してください。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てています —— 最初のプログラムから、リファレンス、bless による OO、そしてよくある落とし穴まで。各セクションは 8〜14 のサブトピック(各 5〜20 行)に分かれ、合計約 150 トピックです。コードスニペットは意図的に短く自己説明的で、コピーしてそのまま `perl` で実行できます。 すべての処理はブラウザ内で行われます —— アップロードもトラッキングもありません。このページは GuruToolkit の無料開発者ツールコレクションの一部です。コードスニペットは自由に使用できますが、いかなる保証もありません。

バージョン 2.1.0