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 |
● 配列
先頭に @
を付けて宣言します。()
を使用して配列を初期化できます。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); |
$a = $b; |
i = aa[0], j = aa[1], k = aa[2]; |
($i, $j, $k) = @aa; |
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"}; と実行すると ship という結果が出力されます。 |
ハッシュ関数 |
意味 |
keys |
すべてのキーを出力します。出力順はランダムです。 @vehicle_key = keys (%vehicle); print @vehicle_key; ground sea sky という結果が出力されます。 |
values |
すべての値を出力します。出力順はランダムです。 @vehicle_val = values (%vehicle); print @vehicle_val; car ship airplane という結果が出力されます。 |
each |
1組のキーと値を出力します。どのペアが取り出されるかが不定なので |
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 ------- |
--- 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"); |
$s = "foo"; $s .= "bar"; |
strlen("foo"); |
length("foo"); |
文字列を分割する | $word = "cat:dog: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 |
+ |
前にある文字の1回以上を表します。 ab+ -> ab |
? |
前にある文字の0, 1回を表します。 ab? -> a |
^ |
指定した文字から始まる ^ap -> ap で始まる文字列がマッチ |
$ |
指定した文字で終わる le$ -> le で終わる文字列がマッチ |
[ ] |
指定した複数の文字の中のいずれかを表します。 [abc] -> a, b, c がマッチ 文字列の場合は、(be|is|was|been) という具合に ( ) | を使用する |
{ } |
前にある文字の指定した回を表します。 ab{3} -> abbb にマッチ |
( ) |
グループ化 |
\w, \d, \s ... |
ほかにもありますがここでは省略 |
構文 |
意味 |
if (/パターン/) if ( 文字列 =~ /パターン/) if ( 文字列 !~ /パターン/) |
デフォルト変数 $_ の中に「パターン」が含まれていれば真 「文字列」の中に「パターン」が含まれていれば真 「文字列」の中に「パターン」が含まれていなければ真 |
if ( 文字列 =~ /^パターン/) if ( 文字列 =~ /パターン$/) if ( 文字列 =~ /[a-z]/) if ( 文字列 =~ /[0-9]/) |
「文字列」が「パターン」で始まっていれば真 「文字列」が「パターン」で終わっていれば真 「文字列」に小文字のアルファベットが含まれていれば真 「文字列」に数字が含まれていれば真 |
s/パターン/置換文字列/ |
「パターン」にはじめにマッチする文字列を「置換文字列」に置き換える 「パターン」にマッチするすべての文字列を「置換文字列」に置き換える |
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") if ($i > 10); |
● ファイル操作
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"); |
open (SMAIL, '|/usr/lib/sendmail -t'); print SMAIL "To: user@bar.com\n"; |
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 |
文字列から前後の空白をとるには? |
$string =~ s/^\s*(.*?)\s*$/$1/; |
標準エラー出力を標準出力に出力するには? |
open(STDERR, ">&STDOUT"); |
標準エラー出力と標準出力をファイルに出力するには? |
$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 |
第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"; } |