C言語を知る人へのPerlの基礎

Perl の解説は、いろいろあるので、ここでは、 C言語を知っている人が Perl を使うことに重点を当てて比較しながら説明します。UNIX ユーザー対象なので正規表現についても知っているものとして説明します。ううむ、するとあまり書くことないかなあ...

* はじめに

Perl は、インタプリタ言語で、記述された Perl プログラム(スクリプト)を perl コマンドが実行していきます。プログラムの記述は、ファイルの先頭に #!/usr/bin/perl というように perl コマンドを指定する必要があります。この記述は、Shell スクリプトと同じです。

C言語
Perl
#include <stdio.h>
 
main()
{
    printf("Hello World!\n");
}
#!/usr/bin/perl
 
print "Hello World!\n";

Perl では、@_, $_, $1, $2, \1, \2 ... といった特殊な変数(配列)があります。Shell スクリプトなどにも特殊な変数はあるので、それなりになじみはあると思います。ただし Perl の場合、特に変数が省略されているような記述では $_ がデフォルトの変数として暗黙のうちに使われてます。

* 比較

● 変数

先頭に $ を付けて変数を定義します。Perl では、型という概念はないので同じ変数に数字や文字列を入れることができます。

C言語
Perl
char   *s = "foo";
int     i = 0;
long    l = 123456;
double  d = 1.0;
$s = "foo";
$i = 0;
$l = 123456;
$d = 1.0;
int i = 0;
. . .
i = 1.0;    /* wrong */
i = "foo";  /* error */
$i = 0;
. . .
$i = 1.0;    # ok
$i = "foo";  # ok
&i;
\$i;
 
$a = 20;     # 変数 $a
$ra = \$a; # $aへのリファレンス $raを作成
print $$ra; # print $a; と同じ

 

● 配列

先頭に @ を付けて宣言します。() を使用して配列を初期化できます。C言語では、宣言したサイズの配列しか利用できませんが、Perl ではサイズを超えた時点で自動的に拡張されます。

C言語
Perl
int a[] = {1,2};
char *c[] = {"abc", "def"};
@a = (1,2);
@c = ("abc", "cde");
int a[2];
a[1] = 3;      /* ok */
a[2] = 3;      /* error */
@a = (1,2);
$a[1] = 3;  # ok
$a[2] = 3;  # ok
char *data[3] = "apple", "lemon", "grape";
int   i;
for (i=0; i<3; i++) {
    printf("%s\n", a[i]);
}
@data = ("apple", "lemon", "grape");
foreach $item (@data) {
  print "$item\n";
}
 
@array = (1, 2, 3);
$ref_array = \@array;
 
$item1 = $ref_array->[0];   # $$ref_array[0]

文字列のコピーや配列のコピー、配列から要素を取り出す方法。

C言語
Perl
strcpy(a,b);
memcpy(aa, ab, SIZE);
$a = $b;
@aa = @ab;
i = aa[0], j = aa[1], k = aa[2];
i = 1, j = 2;
($i, $j, $k) = @aa;
($i, $j) = (1, 2);

Perl の配列の操作では、 配列の先頭の要素を取り出す shift、配列の最後に要素を追加する push、配列の最後の要素を取り出す pop などがあります。また、配列を変数に代入すると、その変数には配列の要素数が代入されます。

構文
結果
@data = ("apple", "lemon", "grape");
$item_num = @data;
$item = shift @data;
push @data, "orange";
要素が三つの配列を定義
$item_num == 3
$item には "apple"、@data は lemon, grape
@data は lemon, grape, orange

 

● ハッシュ

ハッシュとは、連想配列と呼ばれ 「キー」と「値」を1組のペアとして関連付けされた配列です。配列自体は順序付けされていません。% で始まる変数がハッシュ変数となります。ハッシュなんて C言語ではないですね。C++言語だと、STL の map class に相当するかな(map <string, string>)。

構文
意味
%vehicle = ("ground", "car",
            "sea", "ship",
            "sky", "airplane");
%vehicle に ground, car と sea, ship と sky, airplane  という
文字列をそれぞれペアで代入しています。
%vehicle = ("ground" => "car",
            "sea" => "ship",
            "sky" => "airplane");
%vehicle に キー:ground, 値:car と キー:sea, 値:ship と
キー:sky, 値:airplane を代入しています。
$vehicle{"ground"} = "car";
$vehicle{"sea"} = "ship";
$vehicle{"sky"} = "airplane";
変数 $vehicle{"ground"}に car、$vehicle{"sea"}に ship、
$vehicle{"sky"}に airplane を代入しています。
 
上記はすべて同じことを行っています。
print $vehicle{"sea"};
と実行すると sea という結果が出力されます。

ハッシュ関数
意味
keys
すべてのキーを出力します。出力順はランダムです。
  @vehicle_key = keys (%vehicle);
  print @vehicle_key;
 
ground sea sky という結果が出力されます。
values
すべての値を出力します。出力順はランダムです。
  @vehicle_val = values (%vehicle);
  print @vehicle_val;
 
car ship airplane という結果が出力されます。
each
1組のキーと値を出力します。どのペアが取り出されるかが不定なので
通常 while を併用します。 while (($key, $val) = each (%vehicle)) { print "Key:$key Value:$val\n"; }
exists
指定したキーが存在するか確認します。
  exists $vehicle{"sky"};
delete
特定の要素を削除します。
  delete $vehicle{"sky"};

 

● 関数コール(サブルーチン)

Perl では、キーワード sub を使用して関数を定義できます。呼び出す場合、先頭に & を付けて呼び出します(名前に曖昧さが無い場合は & を省略する事ができるが、defined() や undef() の引数として使うようときは & は省略する事は出来ません)。引数は、配列 @_(または $_[0], $_[1], ...) に入っているので必要に応じて別の変数に取り出すなりして使用します。return 文で指定した値が戻り値となるが、指定しなかった場合は最後に評価された式の値が戻り値となります。

C言語
Perl
#include <stdio.h>
 
main()
{
    int val;
    val = add(12, 13);
    printf("%d\n", val);
}
 
add(int a, int b)
{
    return(a+b);
}
#!/usr/bin/perl
 
$val = &add(12, 13);
$print $val . "\n";
 
sub add {
    my ($a, $b) = @_;
    $a + $b;
}
{
    int a[5] = {1, 2, 3, 4, 5};
    ...
    array_data(a);
    ...
}
 
int array_data(int a[])
{
    ...
}
@data = (10, 11, 12);
&array_data(\@data);
 
sub array_data {
    my ($ref_array) = @_;
    my ($item);
 
    foreach $item (@$ref_array) {
        ....
    }
}

Perl では、変数の有効範囲が広域のため問題になることがあります。そこで、変数に my 宣言を行いブロック ( {} で囲まれた部分)内やサブルーチン内でだけ有効なものなものにします。

構文
結果
$x = 12;
$y = 12;
print "main: x = $x, y = $y\n";
&arg_x();
print "main: x = $x, y = $y\n";
 
sub arg_x {
  my $x = 30;
  print "sub: x = $x, y = $y\n";
  $y = 30;
  print "sub: x = $x, y = $y\n";
}
main: x = 12, y = 12
sub:  x = 30, y = 12
sub:  x = 30, y = 30
main: x = 12, y = 30
 
サブルーチンの変数 $x が my によってサブルーチン内だけで
有効なことがわかります。

ライブラリについては、 C言語の場合、単体コンパイルしてアーカイブを作るのでちょっと使い方などが異なるけど.....

C言語
Perl
--- Graphics.c -------
void Shape() { ... } char *Circle() { ... } --- Graphics.h ------- void Shape(); char *Circle(); --- main ------------- #include "Graphics.h"   main() { ... Shape(); ... }
--- Graphics.pl -------
# 変数の重複などのチェックを避けるため
# 空間名をつける
package Graphics;
 
sub Shape() {
    ...
}
 
sub Circle() {
    ...
}
 
1;     # ライブラリが正常に読み込めた事を表わす
 
 
--- main -------------
require 'Graphics.pl';
 
 ...
&Graphics::Shape();
 ...

 

● 文字列操作(正規表現)

Perl の文字列操作を知ってしまうとコンパイラ系の言語でも使えたらなあ〜と思えるぐらい便利です。Java や C# の String Class などは正規表現が扱えるものもあるけどね...

C言語
Perl
strcmp("foo","bar") == 0
strcmp("foo","bar") != 0
strcmp("foo","bar") < 0
strcmp("foo","bar") > 0
"foo" eq "bar"
"foo" ne "bar"
"foo" lt "bar"
"foo" gt "bar"
strcpy(s,"foo");
strcat(s,"bar");
strcat(s,"\n");
$s = "foo";
$s .= "bar";
$s .= "\n";
strlen("foo");
length("foo");
文字列を分割する
$word  = "cat:dog:bird";
@words = split(/:/, $word); ($a, $b, $c) = split(/:/, $word); # @words には cat, dog, bird # $a には"cat", $b には"dog", $c には"bird"
複数の文字列を連結する
@words = ("cat", "dog", "bird");
$word  = join(":", @words);
   # $word には "cat:dog:bird"
行末の改行コードを削除する
$line = "hello, world\n";
chomp($line);
   # $line には "hello, world"

正規表現について何も書かないのも....すべて説明すると長くなるので Perlでよく使いそうな例をあげておきます。

メタキャラ
意味
.
任意の一文字を表します。
 c..l  -> cool, cowl, cull などがマッチ
*
前にある文字の0回以上を表します。
  ab*   -> a
ab
abb abbbbbbbbbbbbb などがマッチ
+
前にある文字の1回以上を表します。
  ab+   -> ab
abb abbbbbbbbbbbbb などがマッチ
?
前にある文字の0, 1回を表します。
  ab?   -> a
ab がマッチ
^
指定した文字から始まる
  ^ap   -> ap で始まる文字列がマッチ
$
指定した文字で終わる
  le$   -> le で終わる文字列がマッチ
[ ]
指定した複数の文字の中のいずれかを表します。
  [abc] -> a, b, c がマッチ
  文字列の場合は、(be|is|was|been) という具合に ( ) | を使用する
{ }
前にある文字の指定した回を表します。
  ab{3}   -> abbb にマッチ
ab{3,} -> abbb, abbbb, abbbbbbbb など 3回以上繰り返されたものにマッチ ab{3,5} -> abbb, abbbb, abbbbb にマッチ
( )
グループ化
1. 特殊変数 $1 の利用 $_ = "data=hello world"; /(.+)=(.+)/ ; print "Name= $1, Value= $2 \n";
( ) でくくられた文字列が特殊変数に割り当てられるので
Name= data, Value= hello world という結果が返ります。
2. 特殊変数 \1 の利用 /(..)\1/
( ) でくくられた文字列が特殊変数に割り当てられるので
abab, cdcd など2文字からなる文字列が2回以上続いていればマッチ
\w, \d, \s ...
ほかにもありますがここでは省略

構文
意味
if (/パターン/)
if ( 文字列 =~ /パターン/)
if ( 文字列 !~ /パターン/)
デフォルト変数 $_ の中に「パターン」が含まれていれば真
「文字列」の中に「パターン」が含まれていれば真
「文字列」の中に「パターン」が含まれていなければ真
if ( 文字列 =~ /^パターン/)
if ( 文字列 =~ /パターン$/)
if ( 文字列 =~ /[a-z]/)
if ( 文字列 =~ /[0-9]/)
if ( 文字列 =~ /[^0-9]/)
「文字列」が「パターン」で始まっていれば真
「文字列」が「パターン」で終わっていれば真
「文字列」に小文字のアルファベットが含まれていれば真
「文字列」に数字が含まれていれば真
「文字列」に数字以外が含まれていれば真
s/パターン/置換文字列/
s/パターン/置換文字列/g
「パターン」にはじめにマッチする文字列を「置換文字列」に置き換える
「パターン」にマッチするすべての文字列を「置換文字列」に置き換える
tr/対象文字/変換文字/
tr/[A-Z]/[a-z]/
tr/[A-Za-z]//d
tr/[A-Za-z]//cd
「対象文字」を「変換文字」にすべて変換する
大文字をすべて小文字に変換する
アルファベットを削除
アルファベット以外を削除

 

● 条件分岐、ループ

基本的には C言語と変わりません。

C言語
Perl
if (...) {
    /* code */
} else if (...) {
    /* code */
} else {
    /* code */
}
if (...) {
    # code
} elsif (...) {
    # code
} else {
    # code
}
while (i < 10) {
    /* code */
}
 
for (i = 0; i < 10; i++) {
    /* code */
}
 
do {
    /* code */
} while (i < 10);
while ($i < 10) {
    # code
}
 
for ($i = 0; $i < 10; $i++) {
    # code
}
 
do {
    # code
} while ($i < 10);
 
do {
    # code
} until ($i >= 10);
 
do {
    # code
} unless ($i >= 10);
 
foreach $i (@a) {
    # code
}
while (...) {
    if (...) {
        continue;
    }
    if (...) {
        break;
    }
}
while (...) {
    if (...) {
        next;
    }
    if (...) {
        last;
    }
    if (...) {
        redo;
    }
}
if (i > 10)
print("\n"); while (...) { if (...) continue; if (...) break; }
print("\n") if ($i > 10);
while (...) { next if (...); last if (...); }

 

● ファイル操作

C言語と似たような手続きを行うのかと思えば、シェルスクリプトのような記述も行えます。

C言語
Perl
FILE *fp;
fp = open("file", "r");
fp = open("file", "w");
fp = open("file", "a");
 
open(FP, "file");
open(FP, ">file");
open(FP, ">>file");
if ((fp=open("file", "r")) == NULL) {
    printf("file open error");
    exit(0);
}
open(FP, "file") || die("file open error");
main()
{
    int c;
 
    while ((c = getchar()) != EOF)
        putchar(c);
}
while (<>) {
    print;
}
 
# Perl の デフォルトのファイルハンドラは
# STDIN, STDOUT, STDERR
# デフォルト変数が $_ で省略可能なので
# 上記は実際には以下のものが省略されています。
#
# while (($_ = <STDIN>)) {
#     print STDOUT $_;
# }
fp = popen("/usr/lib/sendmail -t", "w");
fprintf(fp, "To: user@bar.com\n");
fprintf(fp, "From: user@foo.com\n"); fprintf(fp, "Subject: test mail.\n"); fprintf(fp, "\n"); fprintf(fp, "Hello!.\n"); pclose(fp);
open (SMAIL, '|/usr/lib/sendmail -t');
print SMAIL "To: user@bar.com\n";
print SMAIL "From: user@foo.com\n"; print SMAIL "Subject: test mail.\n"; print SMAIL "\n"; print SMAIL "Hello!.\n"; close (SMAIL);
fp = popen("ls -l", "r");
while (fgets(buf, sizeof(buf), fp) != NULL) {
    fputs(buf, stdout);
}
pclose(fp);
open (FILE, "ls -l |");
while ( <FILE> ){
    print;
}
close (FILE);

 

● 構造体とクラス

C言語の構造体のような記述を行うには、Perl のパッケージを利用します。

C言語
Perl
struct Person {
    char    name[30];
    char    gender[10];
    char    email[50];
};
 
struct Person  p;
strcpy(p.name, "shin");
use Class::Struct;
  
struct Person => {
    name   => '$',
    gender => '$',
    email  => '$'
};
 
my $p = new Person();
$p->name("shin");

C++言語のクラスに似ています。継承が変わっており、メソッド(メンバ関数)は継承するがデータ(メンバ、属性)は継承しません。

ファイル名
コード
クラス定義(Person.pm)
package Person;
 
use strict;
 
# コンストラクタ
sub new {
    # クラス名(パッケージ名)を受け取る
    my $class = shift;
    my ($name, $gender) = @_;
 
    # オブジェクトの属性を定義
    my $self = {
        NAME => $name,
        GENDER => $gender
    };
 
    # クラス名にオブジェクトを関連づける
    bless($self, $class);
 
    return $self;
}
 
# デストラクタ
sub DESTROY {
    my $self = shift;
}
 
# アクセスメソッド
sub set_name {
    my ($self) = shift;
    if (@_) {
        $self->{NAME} = shift;
    }
}
 
sub set_gender {
    my ($self) = shift;
    if (@_) {
        $self->{GENDER} = shift;
    }
}
 
sub get_name {
    my ($self) = shift;
    return $self->{NAME};
}
 
sub get_gender {
    my ($self) = shift;
    return $self->{GENDER};
}
 
1;
クラスの利用(sample1.pl)
<< 実行結果 >>
 
Name is shin
Gender is male
 
Name is momo
Gender is female
use Person;
 
my $him = new Person;
 
$him->set_name("shin");
$him->set_gender("male");
 
print "Name is " . $him->get_name() . "\n";
print "Gender is " . $him->get_gender(). "\n";
print "\n";
 
my $her = new Person("momo", "female");
 
print "Name is " . $her->get_name() . "\n";
print "Gender is " . $her->get_gender(). "\n";
クラス定義(PEmail.pm)
package PEmail;
 
use strict;
use Person;
 
# 継承
@PEmail::ISA = qw(Person);
 
sub new {
    my $class = shift;
    my ($name, $gender, $email) = @_;
 
    my $self = $class->SUPER::new($name, $gender);
    $self->{EMAIL} = $email;
 
    return $self;
}
 
sub DESTROY {
    my $self = shift;
}
 
sub set_email {
    my ($self) = shift;
    if (@_) {
        $self->{EMAIL} = shift;
    }
}
 
sub get_email {
    my ($self) = shift;
    return $self->{EMAIL};
}
 
1;
クラスの利用(sample2.pl)
<< 実行結果 >>
 
Name is shin
Gender is male
Email is shin@where.jp
 
Name is momo
Gender is female
Email is momo@where.jp
use PEmail;
 
my $him = new PEmail;
 
$him->set_name("shin");
$him->set_gender("male");
$him->set_email("shin\@where.jp");
 
print "Name is " . $him->get_name() . "\n";
print "Gender is " . $him->get_gender(). "\n";
print "Email is " . $him->get_email(). "\n";
print "\n";
 
my $her = new PEmail("momo", "female", "momo\@where.jp");
 
print "Name is " . $her->get_name() . "\n";
print "Gender is " . $her->get_gender(). "\n";
print "Email is " . $her->get_email(). "\n";

 

* ちょっとしたテクニック

Perl のバージョンに注意が必要なときには?
require 5.6.0;
 
変数の利用を厳密に行いたいときには?
Perl を使う上では、コードのはじめに必ずuse strictを宣言しておくことを推奨します。
これにより、宣言無しの変数利用の禁止など安全でない構文を制限します。
 
#!/usr/bin/perl
 
use strict;
 
my $var_def;
$var_def   = "okey";  # OK
$var_undef = "ng"; # ERROR
 
文字列から前後の空白をとるには?
$string =~ s/^\s*(.*?)\s*$/$1/;
 
標準エラー出力を標準出力に出力するには?
open(STDERR, ">&STDOUT");
$| = 1;
 

標準エラー出力と標準出力をファイルに出力するには?

$stdfile = "stdout.txt";
open(OUT,"> $stdfile");
select(OUT);
$| = 1;
open(STDERR,">&OUT");
select(STDERR);
$| = 1;
 
ファイルが存在するか確認するには?(Shell の test と同じ書き方)
if (-f $file) {
    print "ファイルは存在しています。\n";
}
 
配列(またはハッシュ)を二つ以上引数に渡したいときは?
リファレンスを使用します。
 
func(\@names, \%vehicle);
 
sub func {
  my $ref_array = shift;
  my %ref_hash  = %{shift()};
  foreach ( @$ref_array ){...}
  foreach ( keys %ref_hash ) {...}
};
 
ちょっとわかりにくい記述なってしまいます。こんな記述が嫌なら Ruby を使ってね!
 
数字かどうか判断するには?
/\D/                              # 数字以外が含まれている
/^[+-]?\d+$/                      # 整数
/^-?(?:\d+(?:\.\d*)?|\.\d+)$/     # 実数
 
配列の最後の要素を得るには?
配列を@arrayとした場合
$last = $array[-1];
または
$last = $array[$#array];
 
変数が定義されているか調べるには?
my ($a, $b, $c) = split(/:/, $line);
if (defined($c)) {
  print "Defined!\n";
} else {
  print "Not defined.\n"
}
の場合、$line が "abc:def:" なら "Defined!" を表示し
        $line が "abc:def" なら "Not defined." を表示する
 
配列を未定義にするには?
undef @array;
 
CGI作成中の「500 Internal Server Error」を極力減らすには?
構文エラーについては、作成後、コマンドラインでそのまま CGI を実行することで不具合を
探すことができます(または、perl -cw sample.cgi)。
では、実行時のエラーはどうしたらよいでしょうか?
それには、eval を利用します。
 
$ans = eval { $x / $y; };
if ($@) { warn "WARNING: $@"; }
 
とすることで、0除算すると警告が出ます。
 
指定した URL にジャンプする CGI を作成するには?
#!/usr/bin/perl
print "Location: http://www.domain-name.jp/\n\n";
 
3文字づつ区切るには?
$string = "aaabbbcccdddeeefff";
@words = $string =~ m/.{1,3}/g;
foreach $list (@words) {
    print "$list\n";
}
 
3桁ずつカンマで区切るには?
$num = 12345678.99;
 
if ($num =~ /^[-+]?\d\d\d\d+/g) {
    for ($i = pos($num) - 3, $j = $num =~ /^[-+]/; $i > $j; $i -= 3) {
        substr($num, $i, 0) = ',';
    }
}
 
print "$num\n";       # 12,345,678.99
 
四捨五入するには?
$num = 12345.6789;
 
$num = &round($num, 3);
print "$num\n";       # 12345.679
sub round { my ($num, $decimals) = @_; my ($format, $magic); $format = '%.' . $decimals . 'f'; $magic = ($num > 0) ? 0.5 : -0.5; sprintf($format, int(($num * (10 ** $decimals)) + $magic) / (10 ** $decimals)); }
 
第n曜日は何日かな?(最近 happy monday が増えたので)
#!/usr/bin/perl
 
use strict;
 
my ($week1, $whatday);
my $year  = 2003;    # 年
my $month = 8;       # 月
my $n     = 2;       # 第n番目
my $week  = 1;       # 曜日 (0-6)  0:日曜日
my $week_string = (qw(日 月 火 水 木 金 土))[$week];
 
# その月の1日の曜日を得る
$week1   = getweek($year, $month, 1);
 
$whatday = 1 + ($week - $week1) % 7 + 7 * ($n - 1);
print "$year年 $month月 第 $n $week_string曜日\n";
print "$whatday 日\n";
 
sub getweek {
    # Zellar の公式
    my ($year, $month, $day) = @_;
    if ($month == 1 or $month == 2) {
        $year--;
        $month += 12;
    }
    int($year + int($year / 4) - int($year / 100) + int($year / 400)
        + int((13 * $month + 8) / 5) + $day) % 7;
}
 
配列の配列を作るには?
#!/usr/bin/perl
 
# DATA (data_file)
#   apple   orange    lemon     grape
#   tomato  pumpkin   lettuce   carrot
#   beef    pork      chicken
 
open(DATA, "data_file") || die("DATA File not found.\n");
 
while ($line = <DATA>) {
    chomp $line;
    @data = split(/\s+/, $line);
 
    if (0) {
        # ダメな記述
        # 以下の記述では、@data_array はすべて @data のありかを示すだけなので
        # 最後のデータが何度も参照されてしまう
        $ref = \@data;
        push @data_array, $ref;
    } else {
        # 正しい記述
        # @data の内容を無名配列にしてそのリファレンスを配列に追加する
        $ref = [ @data ];
        push @data_array, $ref;
    }
}
 
foreach $item (@data_array) {
    foreach $item2 (@$item) {
        print "$item2 ";
    }
    print "\n";
}