Hatena::Diary

Perl入門〜サンプルコードによるPerl入門〜 このページをアンテナに追加 RSSフィード

サンプルコード中心のPerl入門です。 (Perl詳細リファレンス,  ビュアー目次,Perlインストール, コマンドプロンプト )
サンプルコードをコピーすれば、そのままPerlで実行して、試すことができます。
デバッガで、サンプルコードをたどれば理解が深まります。 (デバッガの使い方)
Perl5の新しい書き方に完全対応しています。Perl4の記述はありません。
最近の活動1 Mooseによく似た軽量・高速なクラスビルダー Object::Simpleの開発 Object::Simple入門記事
最近の活動2 Mojoの開発を少しだけお手伝い Mojoの個人的なメモ
みんなのブログを読もう! みんなの書いたソースコードをよもう!

2009-11-20

現代的なPerlの記述方法一覧 + α

 Perl5.8以降における標準的なPerlの書き方を解説します。インターネットで検索するとPerl4のころの古い記述がたくさんあります。また書籍などの多くもPerl4の記法で書かれています。Perl4の記法は複雑になりやすく間違いを生みやすいのでこれからPerlを書く人はPerl5の現代的な記法で記述することを強くお勧めします。

strictプラグマとwarningsプラグマ (必須)

 strictプラグマとwarningsプラグマを有効にします。

use strict;
use warnings;

 use strict;とuse warnings;の2行はスクリプトの最初に必ず記述してください。これらはPerlの文法チェックを厳しくするためのものです。面倒だという軽い気持ちでこれを記述しないと後々本当に面倒なことになります。

 use strict;とuse warningsを書かなくてもよいのはワンライナーと呼ばれるコマンドラインスクリプトを記述するときだけだという風に考えてください。

ファイルハンドルにはレキシカル変数を使うこと (本当に特別な用途がない限りは必須)

 ファイルハンドルには宣言したばかりのレキシカル変数を使います。レキシカル変数を宣言してそれをopen関数の第1引数に指定するとレキシカル変数にファイルハンドルが設定されます。

# レキシカル変数
my $fh;
my $file = 'file1';

open $fh, '<', $file
  or die "Cannot open '$file': $!";

while (my $lien = <$fh>) {
    # 行の読み込み
}

 レキシカル変数はスコープを持つということと他の関数に引数として渡すことができるという大きな利点があります。古い解説にあるようなFHや*FHなどのシンボルを使わないようにします。

 またレキシカル変数の宣言をopen関数の中にまとめてしまうのがより現代的であるといえるでしょう。

open my $fh, '<', $file

3引数のopen関数を使う (必須)

 3引数のopen関数を使用するようにします。

open my $fh, '<', $file

 古い解説では2引数のopen関数を解説しているものがありますが使わないようにしましょう。2引数のopen関数はセキュリティ的に脆いので使用してはいけません。

open my $fh, "< $file"; # 2引数のopen関数を使用してはいけない

ファイルオープン時のエラー処理を行う (必須)

 ファイルオープンを行ったときは必ずエラー処理を行うようにします。

open my $fh, '<', $file
  or die "Cannot open '$file': $!";

 open関数は失敗するとundefを返すのでorを使ってエラー処理を行います。$!にはOSが返したエラーメッセージが含まれているのでユーザに見せるエラーメッセージに含めるようにします。

 プログラムをエラーメッセージを表示して終了させるにはdie関数を使用します。他のプログラムから見た場合は終了コードは255になります。

 ファイルオープンに限らずプログラムの外部と通信する場合はかならずエラー処理を行うようにします。外部というのはメモリの操作を除くすべてです。ファイルやネットワークなどが外部になります。

レキシカル変数とサブルーチンの名前には小文字とアンダーバーを使用する (強く推奨)

 「myで宣言されるレキシカル変数」と「サブルーチン」の名前には小文字とアンダーバーを使用します。

# レキシカル変数
my $user_name;
my $search_word;
my $max_database_connection;

# サブルーチン
sub parse_data {
    
}

sub create_table {
    
}

 この記法が良いか悪いかは別にしてCPANにある新しいモジュールのほとんどはこの記法で書かれています。この習慣にしたがっておいたほうがユーザに統一されたインターフェイスを提供できるという点で非常に多くの益があります。

 変数の命名方法には別に記事を書いていますのでこちらも参考にしてください。

 <変数に適切な名前をつける>

 サブルーチンの命名方法は原則として「動詞 + 名詞」です。意味がはっきりとわかるものについてはユーザの利便性を考えて「動詞」だけにしても良いかもしれません。ただし後で困ることがあるので「動詞 + 名詞」にしておくのが無難だと思います。

パッケージ変数には大文字とアンダーバーを使用する (強く推奨)

 パッケージ変数には大文字とアンダーバーを使用します。

our $OBJECT_COUNT;
our $CLASS_INFO;

パッケージ変数は使わずにレキシカル変数を使うようにする (必須)

 もしあなたがモジュールの作者でないのであればパッケージ変数を使う機会はありません。もし単体のスクリプトの中でパッケージ変数を使っているとしたらそれは間違った使い方です。myを使ったレキシカル変数に変更しましょう。

標準的(?)なコードのフォーマットで書く (少し推奨)

 コードの書き方には好き嫌いがあるのですが、Perlベストプラクティスで紹介されている書き方やPerltidyと呼ばれるコード整形ツールが出力する形式にあわせておいたほうが幾分よいと思います。

 サンプルとしてMojo::URLソースコードのリンクを張っておきます。これをまねして書けば覚えられます。

 <Mojo::URLのソースコード>

 まねするポイントを少しだけ書いておきます。

1. ifやforeach文やサブルーチンなどのスペースの入る位置を見る
if ($flg) { # ifの直後にスペースがあって、()の中にはスペースがないなど
    
}
2. コメントの書き方やスペースの入れ方などを見る

 コメントの書き方や行のスペースの入れ方などをみましょう。CPANに存在するほとんどのモジュールには親切なコメントがないのですが、個人的は簡潔なコメントをソースコードに書いてくれるとありがたいと思っています。

3. タブは使わないでインデントの幅はスペースで4

 一応これはPerlベストプラクティスでいわれていることで、わたしもこの通りにしていますが、タブを使いたいという人も中にはいると思いますので、これは個人の好みで。

日本語などのマルチバイト文字を適切に扱うためにEncodeモジュールを使用する (強く推奨)

 日本語などのマルチバイト文字を適切に扱うにはEncodeモジュールを使用します。こちらは記事にしましたのでリンクを張っておきます。

 <Encode 日本語などのマルチバイト文字列を適切に処理する>

 古い解説にあるようなJcode.pmやJcode.plを使うような手法は現在では推奨できません。Perl5.8以降はEncodeモジュールを使用するのが標準的で問題が少ない方法です。

デフォルト変数 $_ は使用しない (強く推奨)

 Perlにはデフォルト変数 $_ というものが存在します。デフォルト変数は関数に引数を指定しなかった場合に暗黙的に受け取る変数です。プログラムの中で使用すると可読性が落ちるので使うのは控えましょう。

 デフォルト引数を使用するのは次の場合だけです。

1. ワンライナー

 ワンライナーの中では使用してもよいと思います。printの引数や正規表現の対象として$_が利用されています。

perl -ne "print if /AAA/"; # AAAという文字を含む行を取り出すワンライナー
2. map関数とgrep関数

 map関数やgrep関数には$_ がわたってきますのでこれは利用せざるをえないです。

my @greped_array = grep { $_ =~ /AAA/ } @array;
my @mapped_array = map  { $_ * 2 } @array;

 有名なCPANモジュールの中にはデフォルト変数を使用しているものがありますが個人的には推奨しません。できるだけ明示的であったほうが可読性の高いプログラムになります。

foreach文ではレキシカル変数を宣言する (強く推奨)

 Perl5ではforeach文の先頭でレキシカルを宣言することができます。

my @students = ('taro', 'kenji', 'naoya');
foreach my $student (@students) {
    # 処理
}

 この例の場合は@studentsの各要素が$studentに入ってきます。これは$studentはレキシカル変数でforeachのブロックの先頭から終わりまでのスコープを持ちます。

 レキシカル変数を省略するような書き方もできますが推奨しません。

# 推奨できない書き方
foreach (@students) {
    # デフォルト変数 $_ に@studentsの各要素が入ってくる 
}

コマンドライン引数の受け取りかた (参考)

 コマンドライン引数はこんな感じで受け取るのがよいです。

# コマンドライン引数がひとつの場合
my $file = shift;
# コマンドライン引数が複数の場合
my ($file, $option) = @ARGV;

サブルーチンの引数の受け取り方 (参考)

 コマンドライン引数の場合と同様になります。

# 引数がひとつの場合
sub func {
    my $file = shift;
}
# 引数が複数の場合
sub func {
    my ($file, $option) = @_;
}

日付処理の標準モジュールを使用する (参考)

 もしPerl5.10以降を使用しているならTime::Pieceというモジュールが標準で添付されており日付処理に使えます。

 <Time::Piece - 日付・時刻を扱うための標準モジュール>

 またCPANからインストールできる環境であればDataTimeモジュールをインストールするのも良いかもしれません。こちらは高機能ですが少し重いです。

 <日付を汎用的に扱うモジュール DateTime>

 それもできないならlocaltimeやTime::Localでがんばります。

 <Perlでの日付・時刻の扱い>

不必要なモジュールの読み込みは行わないこと (必須)

 他のプログラムのソースコードをコピーしてきた場合にそのプログラムでは使用しないのに余計なモジュールが読み込まれている場合があります。これは後で読んだ人に対していらぬ誤解を招くので必ず削除するように心がけましょう。

use File::Spec;                  # 他のプログラムからソースコードをコピーしてきたために
use File::Basename qw/basename/; # 不必要なモジュールの読み込みが残る場合があるので
                                 # 気をつける

Perlのドキュメントの書き方 (参考)

 仕事で使用する小さなスクリプトの場合はスクリプトの中にドキュメントを埋め込んでおくのがよいと思います。CPANモジュールの場合はソースコードの末尾がドキュメントになりますが、小さなスクリプトの場合は先頭に書いておくと利用者がソースコードを開いたときにぱっとみることができるので便利です。

 PerlのドキュメントはPODと呼ばれる記法で書きます。簡単な書き方だけ紹介しておきます。「=head1」というのが見出しになります。「=head1」の右にタイトルを書きます。その下に一行あけて本文を書きます。一行あけるというのには意味があるので注意してください。ドキュメントの終わりは「=cut」という行になります。英語で書いた場合は次のようになります。

=head1 SCRIPT NAME

SomeScript.pl

=head1 DESCRIPTION

This script is used to do ....

=head1 USAGE

perl SomeScript.pl file1 file2 ...

=cut

# ソースコードの始まり
use strict;
use warnings;

 ローカルのWindowsだけでしか使わないような場合は同僚にわかりやすく伝えるために日本語で書いてもよいと思います。ただしUnixなどのサーバなどでも使う必要がある場合は文字コードに依存させないために英語で書くようにしましょう。

=head1 スクリプト名

SomeScript.pl

=head1 概要

〜するためのものです。

=head1 使用方法

perl SomeScript.pl file1 file2 ...

=cut

# ソースコードの始まり
use strict;
use warnings;

コメントの#の嵐は避ける (推奨)

 よく#だらけのコメントを仕事をしててみるのですが個人的にはお勧めしません。一番の理由は一度そのコメントの記述を行うと後から来た人がそれをまねしないといけないからです。関数ひとつ記述するのにこれをまねしないといけないのかと思うと気持ちが萎えます。またコードの品質を上げるどころか関数を書き換えたときにコメントが追いついていないということが頻繁に起きます。ですのでやめましょう。

#################################################################
# 関数名     : ほにゃらら                                       #
# 引数       : 引数1 引数2                                      #
# 戻り値     : ほにゃらら                                       #
# 作成日時   : あああああ                                       #
# 作成者     : ほれほれ                                         #
# 関数の説明 : いいいいいい                                     #
# 更新履歴   : その1                                            #
#            : その2                                            #
#            : その3                                            #
#################################################################
sub func {
    
}

 こちらの書き方をお勧めします。

# 簡単な解説(1行で)
sub func{
    
}

 <Mojo::URLのソースコード>

も参考にしてください。

文字列リスト演算子 (参考)

 文字列リスト演算子はよく使用されるので解説しておきます。文字列リスト演算子は文字列の配列を作成するのによく利用されれます。

my @strings = qw/aa bb cc/;

 次の記述と同じ意味があります。

my @strings = ('aa', 'bb', 'cc');

モジュールの関数をインポートするときは明示する (強く推奨)

 モジュールで関数をインポートするときは明示するようにしたほうがよいでしょう。ソースコードを読んだ人がその関数はどのモジュールのものなのかを簡単に理解することができます。

use File::Basename qw/basename/;
use File::Copy qw/copy move/;
use File::Path qr/mkpath/;
use Encode qw/encode decode/;

# mkpath関数を使用する。
mkpath($dir);

 もし明示的なインポートの記述がなかった場合はどうなるでしょう。

use File::Basename;
use File::Copy;
use File::Path;
use Encode;

mkpath($dir); # これはどこからインポートされた? 

 このような場合はuseされているすべてのモジュールのドキュメントを読むということになりかねません。あなたは関数がどのモジュールからインポートされたのかを知っているかもしれませんが、ソースコードを読む人には明示的ではありません。ですのでインポートする関数はどんなにあなたにとって明らかに思えても明示的に指定するようにしましょう。

gotoは使用しない (本当に特別な場合を除いて必須)

 Perlにはgoto文がありますが使用してはいけません。gotoを使うプログラミングはPerlに限らずもう過去のものです。もしあなたが何らかの理由でgotoを使いたくなった場合代替する手段は必ず用意されていると思ってください。

 ループ制御を行いたいなら「last」「next」を使用してください。エラー処理を行いたいならdieを使って例外を投げてください。

 (唯一gotoを本当にしようしなければならない局面はAUTOLOADなどの仕組みを使って関数を呼び出したいが呼び出しもとの情報を付け加えたくないという本当に特殊な場合だけです。)

do 〜 whileは使用しない (推奨)

 do whileで記述できる文はwhileで必ず記述できます。do whileを使ったからといって記述が簡潔になるかといえばそうでもないです。逆に普段使用していない分だけ意図がわかりにくくなると感じます。

 do whileで記述できる文はwhileで必ず記述できるのでwhileを使うロジックを考えることをお勧めします。

redoは使用しない (推奨)

 Perlにはredo文がありますが、redoを使わなくても同じロジックを記述することができます。redoは何回か使用したことがあるのですが、redoを使ったプログラミングはとてもわかりにくくなると感じます。redoを使用しなくても同じロジックは必ず記述できるのでredoを使わないロジックを考えることをお勧めします。

プロトタイプは使用しない (強く推奨)

 サブルーチンを定義するときにプロトタイプという型を指定できる機能がありますがこれは使用しません。

# プロトタイプは使用しないこと
sub func ($@) {
    
}

 Perlでは明示的に型を指定しなくてもどのような型の引数も受け取れますし、引数の個数もいくつでもかまいません。ですのでプロトタイプで型を指定したり個数を指定したりする必要はまったくないのです。ですので必ずプロトタイプを指定しないサブルーチンの定義を行いましょう。

sub func {
    
}

エラーを伝えるときはundefを戻り値として返却するのではなくdieを使用する。(推奨)

 Perlには例外処理がないと思っている人もいるかもしれません。Javaのような例外オブジェクトというものはありませんが、簡潔な例外機構を備えています。

 まずは旧来のエラー処理であった戻り値にundefを返却する方法を見ます。エラーが発生したときに単独のreturnを記述するとスカラコンテキストの場合はundefがリストコンテキストの場合は空のリスト () が返却されます。

# エラーが発生したときにundefを返却する
sub func {
    my $arg = shift;
    
    # なんらかの処理
    
    # エラー処理
    if ($error) {
         return; 
    }
    return $val; # エラーが起こらなかった場合の正しい値
}

 そして関数を呼び出す側でエラー処理を記述します。

my $val = func();

die "Error" unless $val; # $valが偽値だったらプログラムを終了

 この記述の問題点はfuncを使う人が戻り値のチェックを怠るとプログラムは先に進んでしまうということです。

 ですので現在的なPerlではエラーを伝えるときにdieを使って例外を投げます。

# エラーが発生したときにdieを使って例外を投げる
sub func {
    my $arg = shift;
    
    # なんらかの処理
    
    # dieを使って例外を投げる
    if ($error) {
         die "Error message";
    }
    return $val; # エラーが起こらなかった場合の正しい値
}

 このようにするとfuncを呼び出してエラーが発生したときはエラーメッセージを表示してプログラムは終了します。

# エラーが発生した場合はエラーメッセージを表示してプログラムが終了
func(); 

 プログラムを終了させたくない場合はeval { }; で受けます。これはJavaでいうcatchだと思ってください。エラーが発生した場合は$@という特殊変数にエラーの内容が設定されますので、この変数をチェックすることでエラーが発生したかどうかを調べることができます。

eval { func() };

if ($@) {
    # エラーが発生した場合の処理を記述
}

この記事に関して

 実は結構個人的な意見が入っていたりするので「それは違うと思う」というのがあれば教えてください。「推奨」とだけ書いてあるものがわたしの個人的な意見を多く含んでいます。「必須」と「強く推奨」の部分はPerlの標準的な書き方に沿った記述を行っているつもりです。

コメントに対する回答

itouhiroさんのコメント

Perl5の「foreach」は、myをつけても、myをつけた新変数ではなく、配列内部の変数に直接アクセスしてしまうという変なクセがある。だからforeachに限ってmyは無意味。Perlの文法はこういう例外も覚えないとならない

 変数としてはまったく新しいものです。ですから意味はあります。foreachが少し変なのはその変数の内容に配列の要素のエイリアスが設定されるということです。スカラ値のコピーはPerlの処理の中でも遅い部類に入るためスカラ値のコピーをしたくないというのが理由でだと思います。「パフォーマンスを考慮した結果」の例外だと思います。

 でも実際にプログラムをしてみるとわかりますがそれほど困ることはないです。(値を書き換える場合についてはエイリアスがわたってくるということは意識しないといけませんが。)

ya--madaさんのコメント

相変わらずややこしいというか明示的じゃないのかperlって。sysopenとかTaintとか、バシッと決め付けて欲しいなぁと思うこともある。

 Perlは基本的な文法の部分で覚えることが多いですからそう見えてしまう部分はあると思います。でもある程度たてばかなり自然に読み書きできるようになります。この自然な感じは他の言語にはなかなかないものなのでPerlに愛着がわく部分のひとつでもあります。

通りすがりさんのコメント

プロトタイプを使わない理由を教えてもらえませんか?

 絶対駄目だという理由はないのですがプロトタイプを積極的に使う強い理由がないからです。Perlは動的な言語なのでどのような型の変数でも受け取ることができますし、あらかじめ変数の個数を指定しておく必要もないです。

 C言語やJavaなどで静的な型を指定するのをまねてPerlでプロトタイプを使うようにするならそれは間違いです。Perlのプロトタイプは整数型や小数点型などの型制約の役割を果たしてくれるわけではないからです。

 プロトタイプは非常にややこしい面をとても多く持っています。プロトタイプはメリットよりもデメリットのほうが多いので非推奨としています。ややこしくなるということの例をふたつ挙げておきます。

 ひとつめはメソッド呼び出しの場合はプロトタイプは意味を持たないということ。また&を先頭につけて関数を呼び出した場合も意味をもちません。このように状況によって意味を変えるのでプログラムをややこしくするものになります。

 また次の記述はよく似ているのに意味が変わってしまいます。(プログラミングPerlに載っている例です。)

sub func ($) {

}
func @foo            # 配列の個数がわたる
func "a", "b", "c";  # aだけが渡される。
func("a", "b", "c"); # いきなりコンパイルエラー

diveintounlimitさん

未だに「or die」ってどうなの、Perlの伝統芸だけどもう否定されてるはず。なんか書いてることが古い気がする。

 現代的なPerlではエラーをユーザに伝えるときはdieやcroakを使用して例外を投げるようにします。ですが組み込みのopen関数はエラーを伝えるために戻り値としてundefを返します。ですからこれを or で受けています。

 Perlの「or」は現代のPerlで否定されていません。個人的な意見になりますがifの条件部に実行したい処理を入れるようにはなるべくしたくはないです。if文の中身が本処理のように見えるからです。

# Perlをしている人はこちらの書きかたをすると
if (!open my $fh, '<', $file) {
    die "Cannot open '$file': $!"; # これが本処理に見えてしまうので
}
# こちらを好む人のほうが多い気がします。
oepn my $fh, '<', $file
  or die "Cannot open '$file': $!";

kkobayashiさん

redoがダメならunlessも分かりにくいからダメ?

 unlessは現代のPerlでは気軽に使用されています。「〜でなければエラー」ということを明確に伝えたいという思いがこめられているような気がします。

# こう書くよりは
die "Error message" if !$is_valid;

# こう書く人のほうが多いように思います。
die "Error message" unless $is_valid;

lizyさん

Perlは特定のモジュールを使うかどうかでコーディングスタイルががらりと変わったりするのが好きになれない

 たとえばData::DumperというモジュールのメソッドにはDumperという大文字で始まるメソッドがあったりします。現代的なPerlではメソッド名の開始は小文字で始めることが慣習になっています。

 ですから古いくからあるものでコーディングスタイルがまだ慣習となっていないころに作成されたモジュールの中には少ないながらそのようなものもあります。ですがこのようなものは最近は比較的少なくなっています。ですのでがらりとかわるということはあまりないのではないでしょうか?

通りすがり通りすがり 2009/11/01 23:31 (一応タグ -> タブ)
プロトタイプを使わない理由を教えてもらえませんか?

tokuhiromtokuhirom 2009/11/02 00:03 open の引数をチェックするのはいいのですが、より楽な方法として autodie.pm も最近の perl だとつかえます :)
(対象読者的に言及する必要はないとおもいますが、念のため)

laclefdorlaclefdor 2009/11/02 09:16 これは素晴らしいエントリー。ありがとうございます。

yappoyappo 2009/11/02 14:53 sub foo (&@) {} の件に言及してあると良かったかと思います。(AUTOLOAD の goto に言及してあるので)

otsuneotsune 2009/11/02 18:53 「タグは使わないでインデントの幅はスペースで4」
はindentの文脈だとすると「Tab or space」のことだと思われるので
s/タグ/タブ/

perlcodesampleperlcodesample 2009/11/02 20:01 「タグ」→「タブ」に修正しました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証