Assyのリベラル文学研究所もご覧ください。

Perl

プログラミングの基本が分かる入門者のとびら

Perlは、インタープリタ式のスクリプト言語で、プログラミング入門者から熟練のプログラマまで、多くのプログラマに広く使われている言語です。
実際のところ、「Perlを一度触るとプログラミングの基本が分かる」ということが言えます。
プログラミングを行う上で、プログラミングとはどのようなものなのか、どのようにコードを書いて実行するのか、ということが分かりやすいため、「入門者向き」と言えます。
PerlUNIXのフィルターコマンドやシェルスクリプトsedコマンドなどの考え方をベースとしているため、標準入出力やファイル処理など、Perlの教科書にはUNIXの用語が飛び交うこともしばしばです。そのため、UNIXの基本的な知識を知っていれば、習得の助けとなります。
また、PerlUNIXのフィルターコマンドと同様に、パイプなどで他のUNIXコマンドやシェルスクリプトと組み合わせて、Perlスクリプトを使えます。このため、「このコマンドで実行した結果を簡単に再処理したい」といった場合に、Perlで簡単なスクリプトを書いてその中に「はめこむ」といった使い方をすることができ、システム管理に役立ちます。
C/C++言語などと違い、Perlには変数の型指定がなく、型はその場その場で動的に決まります。ですから、

$sentence=("123"."567")x2;

として"123567123567"という文字列を作った後で、それを

$sentence+=3;

などとし、「数値を文字列にし、その上でまた数値にする」といったことができます。
Perl正規表現も強力で、

@list =(
  "This is a pen.",
  "This is an apple.",
  "It was good.",
  "They are gread people."
);

foreach $sentence(@list) {
  if($sentence =~ /is/) {
    print "$sentence\n";
  }
}

Perl教科書より編集して引用。)
のようにすることで、「リスト@listの中のisという文字列が含まれている要素を表示する」のようなことをパターンマッチングで処理することができます。
正規表現は、たとえばログファイルやUNIXの設定ファイルのように、「ある一定の形式で書かれているテキストを整形する」場合に役に立ちます。これはUNIXでは多くの場合タブや空白で区切られており、場合によってはawksedなどでも処理できるところを、より高度にPerlで行うこともできます。
PerlUNIXと親和性が高く、UNIXシステムでは最初からインストールされていることがほとんどですが、Windowsからも使えます。ActiveState社のActivePerlとStrawberry Perlがあり、僕個人の意見ではStrawberry Perlがおすすめです。ActivePerlのように導入のためにアカウントを作成する必要が無く、msiファイルだけをダウンロードして実行することで簡単に導入でき、必要なバイナリはgccなども含めて全てインストールされるため、CPANのあらゆるモジュールを利用できます。僕もWindowsにおけるPerlはStrawberry Perlを使っています。

ラリー・ウォールの遊び心がいっぱい

Perlの特徴として、Perlの作者のラリー・ウォールの遊び心がいっぱいである、ということが言えます。
各所に面白くするための工夫が見られて、省略など多くの「多様なやり方」があります。
UNIXとも親和性が高く、処理速度もスクリプト言語の中では高速なので、Perlの愛好家は今でもたくさんいます。
最近はPythonRubyなど新しい言語がどんどん増えていますが、彼らは「多様性よりも自然さ」を好み、「自然でない記述は書きづらくする」といったことを行っています。それが悪いわけでは全くありませんが、Perlの遊び心がPythonRubyでは失われているように感じます。

Perlの言語的位置づけ

Perlは、UNIXでは古くから、あるプログラムとあるプログラムの間をノリのようにくっつけて取り持つ「グルー言語」として使われます。
Perl正規表現やファイル処理などを簡単に記述することができ、省略構文やデフォルト変数などを上手く使うことで、強力なテキスト処理・ファイル処理を行うことができます。
こうした理由のため、grep, sed, awkなどでは簡単にできないような、決まりきった定型処理を巨大なテキストや膨大なファイルにバッチ処理のように行う時、シェルスクリプトなどの代わりにPerlを使って柔軟な自動処理を実現できます。
また、Perl正規表現は「ある決まりをもった構造的なデータの処理」を得意としており、そのためログファイル、メールヘッダ、設定ファイルなどを処理・加工することに適しています。
要するに、「UNIXアドミニストレータが楽をするための言語」です。
また、Perlは、Webサーバ上で動的なHTMLを自動生成するCGIと呼ばれる仕組みを開拓した存在です。CGIは昔はPerlで書かれたものが一般的でした。今ではHTMLの中にスクリプトを埋め込めるPHPが登場し、プロセス単位でプログラムを実行するPerl/CGIよりもサーバーの中でスレッドを作成するPHPJavaサーブレットが登場したこともあり、Perlで新しく作られることは少なくなっていますが、インターネット上ではPerlで作られたフリーの配布スクリプトなどが今でも配布されています。こうしたPerl/CGIで書かれたCGIでWebプログラマへの道を志した人も多いと思います。
しかしながら、Perlには欠点があります。それは、記号が多く、記述の自由度が高く、省略構文や他の言語にはない仕様が多いため、難読化されたような「他人には絶対に分からないような分かりづらい(そして醜い)コード」になることが多いのです。
また、Perlオブジェクト指向は後から追加されたものであり、PythonRubyオブジェクト指向に比べてエレガントでありません。
また、今やWebサービスはWebフレームワークで開発することが一般的になっており、LaravelやRuby on RailsDjangoなどが一般的です。特に、人工知能やデータサイエンスのためのライブラリが豊富なPythonを、Djangoと組み合わせてWebサービスを構築することが最近は増えています。クライアントサイドで使われていたJavaScriptを、Node.jsなどを用いてサーバーサイドで使うことも増えています。JavaScriptには欠点もありますが(数値型の種類が少なくクラスベースのオブジェクト指向でないなど)、TypeScriptなどを使うことで補うことができます。
そのような理由で、Perlは既に旧来の言語になろうとしています。
しかしながら、Perlには「言語仕様として面白い要素」がたくさんあります。また、昔はPythonではなくPerlがプログラミング初学者にとっておすすめの言語でした。そのため、プログラマを目指すのであれば、一度学んでおいて損はない言語です。古くからの言語であるため、書籍も多く、ラクダ本のような決定的バイブルもあり、入門的な書籍もたくさんあるため、初心者にとっては学びやすいでしょう。

コンテキストによって変わる人間臭い言語

Perlの文法は文脈(コンテキスト)のよって変化する場合があり、このため「人間臭い言語」として知られている。たとえば、配列へ代入する場合はリストコンテキストで、スカラー変数へ代入する場合はスカラーコンテキストで評価される。一行ずつ読み出すか全体全てを読み出すかがコンテキストによって変わったりする。
以下のコードは、ファイルハンドルFILEから1行読み出して変数$lineに代入する。

$line = <FILE>;

以下のコードは、ファイルハンドルFILEからすべての行を読み込んで配列@textに代入する。

@text = <FILE>;

後日注記:Perlには型がないが、変数が数値として扱われるか文字列として扱われるかはコンテキストによって決定する。

クロージャとしてのグローバル変数

Perlでは、グローバル変数を多用することで、オブジェクト指向を行わなくても、容易にデータの共有ができる。
これは、RubyJavaScriptにおける、クロージャと良く似ている。僕は、これを「グローバル変数による準クロージャ機能」と呼んで良いと思う。
Perlでは、グローバルスコープに変数や関数の宣言が置かれるため、どの関数からも同じ変数を参照することができて、簡単かつ、プログラミング初心者が自分のコードを書く上で、分かりやすい。
だが、これは小規模なスクリプト言語だからなせる業である。もっと大規模なプログラミングを行う上では、どの関数群がどの変数の宣言を共有するのか、きちんと分けて考えないといけない。そのためには、JavaC++のような、名前空間やパッケージ、そしてオブジェクト指向のクラスとそのメンバとインスタンスの考え方の導入が必要である。
これは、CとC++の関係にも言える。Cでは、さまざまな関数で同じ変数を共有するためには、グローバル変数を使うか、あるいは構造体のポインタを使う。プロトタイプ宣言はヘッダファイルに書かれている。だが、この方法では、開発者やその保守をする人が、プログラミングの実装の詳細について詳しく分かっていなければならない。JavaC++オブジェクト指向を使えば、それを隠蔽し、カプセル化することができる。

ラクダ本

Perlはプログラミング初心者に有用である。Perl作者のラリー・ウォールが書いた「プログラミングPerl」というラクダ本オライリーの表紙がラクダなのでそのように呼ばれている)は、今でもプログラミング言語のバイブル的存在である。

Perl 6 (Raku)

新しいPerlPerlの作者ラリー・ウォールが15年の歳月を経てリリースした伝説の新バージョン。
ラリー・ウォールは、Perl 6と呼ばれるPerlの後継バージョンを15年考えて開発した。最近リリースはされたが、本質的なところはPerl 5の雰囲気が残っている。Parrotと呼ばれる「軽量プログラミング言語のための共通仮想マシン」も作られていたが、それはどうなったのか定かではない。だが、Perl陣営は今でも頑張っている、ということは言えるだろう。
Perl 6の正式名称はRakuに決定した。これにより、Perl 6がPerl 5の後継バージョンではなく別の言語であることが明確になった。名称の由来は日本語の「楽」からである。僕の憶測だが、おそらく「ラクダ」からつけたかったのではないかと思う。ラクダは楽だ。

言語の基本

推奨されている記述

Perlでは、以下の二行を常に書くことが推奨されている。

use strict;
use warnings;

strictは潜在的な問題をコンパイル時に調べる。warningsは警告を表示する。

シングルクォーテーションとダブルクォーテーション

Perlでは、文字列を

'text'

とシングルクォーテーションで囲った場合、\'と\\を除いて変数や特殊文字の展開などがされないが、

"$value\n"

とダブルクォーテーションで囲った場合は、変数や特殊文字の展開がされる。
このため、変数の中身を表示したい時は

print "$value\n";

のように実行する。

算術演算子

Perlの算術演算子には、+, -, *, /, %, **などがあるほか、インクリメント・デクリメント演算子である++や--もある。

変数と代入

Perlでは変数の頭に$をつけます。

$price = 6000;
print $price * 1.1;

Perlでは数値や文字列のような単純なデータをスカラーデータと呼び、その変数をスカラー変数と呼ぶ。Perlにはスカラースカラー配列、スカラー連想配列という3つの基本的なデータ型があり、各データ型に対応した形で、それぞれスカラー変数、配列、連想配列(ハッシュ)の3つの変数が存在する。

配列とリスト

配列には@をつけます。

@score = (85, 60, 95);
print $score[0];

1..4のように書くと(1, 2, 3, 4)というリストを表す。また@array[1, 2, 3]と書くとリストの2, 3, 4番目の要素からなるスライスを作ることができる。添え字は0から始まり、$list[0]は一番目の要素を表す。逆からアクセスする時は$list[-1]などのように-1から始まる。

ハッシュ

ハッシュには%をつけます。

%hash = (
    '井上千佳子' => 35,
    '星野サトシ' => 22,
    'ミスターX' => 21,
    'ドナルド・シュバルツ' => 22,
);

foreach $key (keys(%hash)) {
    $value = $hash{$key};
    print "$keyの年齢は$value歳。\n";
}

実行結果:

ドナルド・シュバルツの年齢は22歳。
ミスターXの年齢は21歳。
井上千佳子の年齢は35歳。
星野サトシの年齢は22歳。

ハッシュの特徴として、「キーは重複しない」「値は重複しても良い」「順番は無意味」という特徴があります。
ここでは、順番が無意味なため、Perlが値を高速に検索するために都合の良い順番を勝手に決めてキーと値が表示されています。
リストでは[]を添え字に使うが、ハッシュでは{}を添え字に使う。@arrayの最初の要素は$array[0]となり、%hashのキーschwarzの値は$hash{'schwarz'}となる。値は重複してもよいがキーは重複できないため、同じキーに対する値の代入は追加ではなく上書きになる。

push/pop/shift/unshift

pushとpopはスタックをあつかうための関数。pushは配列の最後に要素を付け加え、popは配列の最後から要素を取り出す。

push(@array, 'LAST_DATA');
$item = pop(@array);

また、shiftは配列の最初の要素を取り出し、unshiftは配列の最初に要素を追加する。
pushとshiftを使うことで、スタックではなくキューを実現できる。

splice

また、配列の途中から要素を抜き出したり、要素を挿入するには、spliceという関数を使う。

splice(対象配列, どこから削除するか, 削除する長さ, 挿入する別のリスト);

spliceを上手く使うことで、配列が楽に操作できる。

reverse/sort

reverseはリストを逆順にする関数。
sortはリストをソート(並び替え)する関数。
リストを逆順でソートしたい場合は、sortしたリストに対してさらにreverseをかける。reverse(sort(@list))のようになる。

srandとrand

rand関数を使うことで、乱数(ランダムな値)を発生させることができる。
rand関数を使う前に、srand関数を使ってrand関数の初期化を行う。
以下は僕の書いたコード。rand()は浮動小数点数を返すため、int()で型を変換している。

for ($i = 0; $i < 10; $i++) {
  srand();
  $x = int(rand(2));
  if ($x == 0) {
    print "攻撃が命中した!\n";
  } elsif ($x == 1) {
    print "攻撃は外れた!\n";
  }
}

forとwhile

繰り返し。

for ($x = 1; $x <= 10; $x++) {
    print "x = $x\n";
}

whileを使って繰り返しを行うこともできる。

$x = 1;
while ($x < 10) {
    print "x = $x\n";
    $x++;
}

またuntil文やdo文を使うこともできる。次の繰り返しの前に何かを行いたい時はcontinueブロックに記述できる。リストの全要素に処理をかけたい時は、インデックスを用いずにforeach文を使う。
foreach文はリストに対してひとつひとつの要素に繰り返し処理を行うことができる。また、ファイルハンドラを使うことでファイルの各行に処理を行うこともできる。
foreach文のひとつひとつのインデックス変数は省略した場合$_からアクセスできる。またインデックス変数は複製ではなく別名であるため、要素を書き換えて代入するためにも使える。
リストの全てに処理を行って別のリストを作りたい時はmap関数を使うのが便利。
C言語におけるbreakはlast、continueはnextが同等の文に相当する。

if ~ elsif ~ else

条件式。

if ($x > 5) {
    print "xは5より大きいです。\n";
} elsif ($x == 5) {
    print "xは5ちょうどです。\n";
} else {
    print "xは5より小さいです。\n";
}

unless文はif文と逆の意味で使える。
条件式は==(等値)が=(代入)とは別の演算子であることに注意しよう。また条件式の範囲に注意。$x >= 3と$x > 3はまったく範囲が異なる。

ファイル入力

ファイル入力。

open(IN, "data_file.txt") or die "$!";
while ($x = <IN>) { print $x; }
close(IN);

Perlではリストが入るべき場所に<FILE>と記述されていると、対応するファイルハンドルから全部の行を読み込む。またスカラーコンテキストで$x = <IN>と記述されているとファイルから一行読み込む。ファイルハンドルはopenで開き、closeで閉じる。
ファイルではなくディレクトリを扱う場合は、opendir, readdir(DIR), closedirなどとする。ファイル一覧の取得にはglob関数も便利。
また、$x = <STDIN>とすることで、標準入力から一行読み込める。代入されるコマンドライン入力行には改行が含まれているので、chomp関数を使って改行を取り除く。
後日注記:or dieはエラー処理。ファイルが存在しなかった場合などに呼ばれる。

ファイル出力

ファイル出力。

open(OUT, "> data_file.txt") or die "$!";
print(OUT "メッセージ投稿1\n");
close(OUT);

掲示板を作る時などは、スレッドを示すファイルにどんどん投稿のHTMLを追記していけば簡単に作れる。
後日注記:ファイル名に>を付加するだけで、書き込みオープンが実現できる。

標準出力

標準出力にschwarzと出力する:

print 'schwarz';

書き込みオープンしたファイルTXTにschwarzと出力する:

print TXT 'schwarz';

サブルーチン

サブルーチンには&をつけます。

sub add {
    return($_[0] + $_[1]);
}
print &add(13, 50);

Perlのサブルーチンでは、関数を呼び出した時に渡す引数は、サブルーチンの中では@_というリストに格納される。これを、$_[0]や$_[1]のようにアクセスできる。@_はサブルーチンの中のローカル変数で、参照はできるが代入はできない。与えられていない要素はundef(定義されていない)となる。
サブルーチンの&は省略することもできます。

サブルーチン2

@_は特殊な配列で、引数が格納されている。それぞれの引数には、$_[0]や$_[1]でアクセスできる。
my変数を用いると、その変数がサブルーチンの内部だけで使われるものであることを表すことができる。

sub calc_average2 {
    my ($x, $y) = @_;
    my $ave;
    $ave = ($x + $y) / 2;
    return $ave;
}

requireとuse

別のファイルのサブルーチンを使うには、requireを使う。requireでファイルを読み込むと、ファイルの中に記述されたサブルーチンが使えるようになる。
モジュールを使う時はuseを使う。

正規表現

$str = 'whoever';
if ($str =~ /who/) {
    print "マッチしました。\n";
else {
    print "マッチしませんでした。\n";
}

実行結果:

マッチしました。

$&は特殊な変数で、マッチした範囲を取り出すことができる。パターンマッチを行うと自動的に設定される。

$str = 'My age is 27.';
if ($str =~ /\d+/) {
    print "$& is the number.\n;
}

実行結果:

27 is the number.

以下は正規表現による置換の例:

$str = "Who is he.\n";
$str =~ s/he/she/;
print $str;

このように、s///構文を使うことで簡単に正規表現による置換ができる。また、パターンの末尾に

$str =~ s/he/she/g;

のように修飾子を付けることができる。

  • /g
    • 繰り返してパターンマッチを行う
  • /s
    • 文字列を一行として扱う(.が改行にもマッチする)
  • /m
    • 文字列を複数行として扱う(改行を区切りとした複数行として扱う)

また、tr///による置換を行うことで、大文字と小文字を変換したりすることができる。

$str =~ tr/A-Za-z/a-zA-Z/;

ほかにも、HTMLタグを消したい場合などは以下のような正規表現が使える。

$str =~ s/<[^>]*>//g;

Perlではデフォルト引数$_を上手く使うことで、まったく変数を使わなくても、「それに対してそれを処理する」ような形で処理を行うこともできる。たとえば、

while (<STDIN>) {
    if(/^ID:/) {
        print;
    }
}

などとできる。
パターンを()でかこって、$1や$2などから複数のマッチした場所を後で使うこともできる。パターンマッチの中で置換する場合などは$1ではなく\1を使うことに注意。

joinとsplit

ちなみに、あるパターンで区切られている文字列をリストにしたい場合は、

@score = split(/,/, '90,95,65');
print "@score\n";

のようにする。これは,で区切られた文字列を複数の文字列にする。また、joinを使うことで、複数の文字列を1つに連結できる。

print join(':', @array);

splitに//を与えた場合、1文字(1バイト)ずつ切り出したリストを作ってくれる。このため、以下のようにできる。

$line = 'Hello, schwarz!';
@array = split(//, $line);
print join(':', @array);

実行結果:

H:e:l:l:o:,: :s:c:h:w:a:r:z:!

リファレンスの基本

リファレンスは、データに直接アクセスするのではなく、データの存在するメモリアドレスをスカラ値として定義する変数。
リファレンスを作るためには、変数に\をつける。リファレンスはスカラー変数となる。

@nums = (10, 20, 30);
$ref1 = \@nums;
$ref2 = $ref1;

$ref1と$ref2は同じ配列を指す。
配列のリファレンスを作成するためには以下のように[]を使うことができる。

$ref = [10, 20, 30];

デリファレンスを行うためには、@{配列のリファレンス}とする。リファレンスがスカラー変数の場合、{}を省略できる。

@nums = @$ref;

さまざまなリファレンス

配列ではなく、スカラー、ハッシュ、サブルーチンの場合は、

\$scalar
\%hash
\&sub

として定義し、デリファレンスするためには

$$scalar_ref
%$hash_ref
&$sub_ref(1, 2)

などとする。

オブジェクト

リファレンスとパッケージを組み合わせることにより、Perlオブジェクト指向プログラミングを行うことができる。
オブジェクトを生成するにはbless関数を使う。
クラスはパッケージにすぎず、オブジェクトはbless関数で作成し、コンストラクタはblessされたリファレンスを返す。また、メソッドは第一引数にオブジェクトを表す変数を取るサブルーチン。これによって継承も委譲もできる。見た目は変だが使いやすい。

メソッドの第一引数

実際にはコンストラクタ、アクセサ、メソッドは第一引数にオブジェクトを取る。よって、

Hoge->new(meth => 'v1');

Hoge::new('Class', meth => 'v1');

を意味し、

$obj->meth;

Hoge::meth($obj);

を意味している。

system関数

system関数を使うことで、Linuxのシェル入力のように新しいプロセスを実行できる。
後日注記:プログラムの実行後の出力を得たい時は、バッククォートを使って`〜`とする。

eval

evalを使うことで、文字列の格納された変数をPerlの文として実行できる。

簡単な計算プログラム

自分で作った簡単なプログラムです。攻撃力が10のキャラと、攻撃力が1だが「1ターンごとに攻撃力が1上がっていく」キャラが戦って、いつ上がっていく方のキャラが上回るか、を計算する。
正確にいうと、「いつ上回るかを計算する」というより、その「上回っていく過程」を表示する。
Rustで書いたコードをPerlに移植しました。

$x = 0;
$y = 0;
$count = 1;

while ($count <= 100) {
  $x += 10;
  $y += $count;
  print "$x\t$y\t$count\n";
  $count++;
}

簡単な説明

Perl

シジル

$はスカラー変数、@はリスト、%はハッシュ、&はサブルーチン

正規表現

「=~」で簡単に正規表現処理ができる

ファイル処理

UNIXに慣れ親しんだ人なら直観的に使いやすいファイル処理を行える