PHP Perl 2021年08月18日 19:51   編集
PerlからPHPへ書き換えからの続きです。

qq()を""へ置換して内部の"をエスケープするのがエディタの正規表現置換ではうまくいかないということで、PHPを使ってその書き換えをすることにしましたが、どうせならついでにほかの置換部分、ダブった変数の書き換えもやれるようにしたのが、perl2php.phpです。
ver.1.0    2014/11/28    機能制限モード追加
ver.1.1    2021/08/18    PHP8でうまく動かないのを修正
久しぶりに更新しました。PHPの仕様変更への対策だけですが。Xampp8.0.3参照
webに置いて使用するようなものではないので、ローカルでのみ実行可能ですが、機能制限版をアップしました。
xamppなどを入れてローカルでPHPを動かせる環境を持ち、自分でperlやPHPを書いている人対象ということになります。使用する場合ははあくまでも自己責任でお願いします。続きを読む
counter:4,405
Perl Xampp 2020年11月10日 12:05   編集
Xamppに同梱されているPerlはバージョンが古いので、ActivePerlの最新版に入れ替えていたが、ActivePerlよりさらに新しいバージョンが公開されているStrawberryPerlに入れ替えてみた。(現在ActivePerlの最新版は5.28だが、StrawberryPerlでは5.32が出ている。)

http://strawberryperl.com/から最新版のstrawberry-perl-5.32.0.1-64bit.msiをダウンロードする。インストール先を指定できるので、今まで使っていたC:\xampp\perlにインストールしてみたが、なんかうまくいかない(c:\usr\local\bin\perlがperl.exeのパスになるためには、C:\xampp\perlではなくC:\xamppにインストールすべきだったような感じ)。
結局C:\xampp\perlは元に戻して、デフォルトのインストール先C:\Strawberryにインストールすることにした。

元々c:\usr\local\bin\perlでperlを参照できるようC:\usr\localC:\xampp\perlのシンボリックリンクを作成していたが、このリンク先をC:\Strawberry\perlに変更すればいいはずだ。すでに作成済みのシンボリックリンクがある場合はいったん削除しておかないと作れないようだ。管理者モードでコマンドプロンプトを起動し、
rmdir C:\usr\local
で既存のシンボリックリンクを削除してから
mklink /D "c:\usr\local" "C:\Strawberry\perl"
を実行して新たなシンボリックリンクを作成する。こうしておけばActivePerlに戻したいときはリンク先を元のC:\xampp\perlに変えれば元に戻せる。

print $];
を実行すると
5.032000
と表示される。無事にアップデートできたようだ。
ところがコマンドプロンプトから
perl -v
で確認すると、5.28ということになってしまうが、古いActivePerlも残したままにしている関係だろうか。CGIの実行には関係なさそうなので、そのままにしておいてもたいした問題ではないとは思うが、いちおう「アプリと機能」で古いActivePerl5.28をアンインストールした。するとコマンドラインでも5.32と表示されるようになった。ただしXamppフォルダに残しておいたC:\xampp\perlフォルダごと消えてしまった。この方がすっきりするか。もちろんC:\usr\localのシンボリックも変更済みなので、CGIの実行にも支障はない。

やはりデフォルトではJcodeモジュールなどが入っていないようで、エラーになるCGIがある。StrawberryPerlではCPANからモジュールインストールもできるらしいが、ActivePerlの時と同じようにC:\xampp\perl\libのJcodeフォルダ、Jcode.pmをC:\Strawberry\perl\libにコピーすると認識されたのでそれで良しとする。

その他の部分では5.24や5.26にアップデートしたときのような問題はなさそうだ。


5.26以前からアップデートしたら、@INCにカレントディレクトリが含まれなくなった仕様変更に引っかかるかもしれない。
require 'init.cgi';
でエラーになったら
require './init.cgi';
と変更するか、スクリプトの最初のほうで
push(@INC,'.');

use lib '.';
または
use FindBin;
use lib $FindBin::Bin;
を追加して、@INCにカレントディレクトリを追加すればいい。
counter:15
Perl 2020年10月15日 14:13   編集
いまさらだが、XamppのPerlをバージョン5.28にアップデートすると、いくつかエラーが発生するようになった。エラーログを見ると
Can't locate init.cgi in @INC (@INC contains: ./lib C:/usr/local/site/lib C:/usr/local/lib)
というようなエラーがあちこちで発生している。init.cgiが@INCの中に見つからないというエラーで、カレントディレクトリにあるモジュールの読込で発生している。確かに@INC contains ではカレントディレクトリは含まれていないようだ。
セキュリティの関係でバージョン5.26から@INCにカレントディレクトリが含まれないようになったらしい。
とりあえず動くようにするには
require 'init.cgi';
というような行でエラーが発生しているなら
require './init.cgi';
に変更して明示的にカレントディレクトリであることを表記して読み込むか、スクリプトの最初の方で@INCにカレントディレクトリを追加しておけばいいようだ。
@INCにカレントディレクトリを追加する方法としては
push(@INC,'.');
あるいは
use lib '.';
などがある。pushだと@INCの最後に、use lib だと先頭に'.'が追加される。
推奨されているのは
use FindBin;
use lib $FindBin::Bin;
これはFindBinモジュールを読み込んで、それによって利用できるようになるパッケージ変数$FindBin::Bin(実行スクリプトのあるディレクトリパス)を@INCに追加することになる。
これだと@INCの先頭に'C:/xampp/htdocs/local/test' のようなパスが追加されることになる。
counter:1,481
CGI Perl 2017年02月08日 13:12   編集
perlのバージョンが新しくなったらCGIが動かなくなったという書き込みがサポート掲示板にあった。
Perl5.24ということだが、ローカルでCGIのテストに使っているXamppは最新版でもPerlのバージョンはは5.16なので、5.24での動作が確認できない。

Perlを5.24にアップデート

単体ではWindows用ActivePerlの5.24が公開されている。
Xamppの環境でこのActivePerlが使えるのかわからないが、取りあえずインストールしてみる。
XamppでPerlがインストールされているC:\xampp\perlに直接インストールしたいところだが、ダウンロードしたActivePerlのインストーラーはインストール先を変更できないようだ(それとも見落としたか?)
また、64bit版のインストーラーを使ったので、C:\Perl64というフォルダにインストールされてしまう。
取りあえずこれをXamppでインストールされたC:\xampp\perlフォルダにコピーすることにする。C:\xampp\perlは別名に変更してバックアップしておき、C:\Perl64の中身をすべてC:\xampp\perlにコピーしてみた。
これだけでうまくいくはずないよなと思ってCGIを実行してみると、あっさりと動いてしまって拍子抜けした。
print $];
でPerlのバージョンを確認してみると、無事
5.024001
と表示され、5.24にアップデートできたことが確認できた。

jcode.plのエラー

Perlをバージョン5.24にアップデートできたので、エラーが出るというCGIを実行してみると確かにエラーが出て実行できない。
エラーログを見てみるとjcode.plで
Can't use 'defined(%hash)'
というようなエラーが出ているようだ。確かにこの日本語変換ライブラリのjcode.plはかなり昔に書かれたものなので、最新のPerlで動かなくなっても不思議ではないかもしれない。Kent-Webなどで公開されているCGIを見てみるとjcode.plの後継ライブラリというjacode.plが使われているようだ。

jacode.plに更新

CPAN http://search.cpan.org/~ina/jacode/で最新版が公開されているようなので、
ダウンロードする。このうちjacode.plをjcode.plに置き換えればいいようだ。jcode.plを使っていたCGIでは
require './lib/jcode.pl'
というようなjcode.plを読み込んでいる部分を
require './lib/jacode.pl'
に変更する。
そのまま互換性があるのかよくわからないが、とりあえずエラーは出なくなった。
調べてみると同じ原因でエラーが出るCGIがけっこうあったので、すべて更新する必要があるようだ。

Jcode.pmを使用する

いまさらだけど、jcode.plのエラーを避けるためにjacode.plに更新ではなく、Jcode.pmを使うという方法もあるようなので追記しておく。Jcode.pmは最近はサーバーのほうに標準モジュールとしてインストールされているケースが多いようだが、Xampp同梱のPerlには入っていないようだ。なのでJcode.pm使う前提で公開するCGIにはライブラリディレクトリに入れておくのが無難のようだ。
例によってCPANでJcode.pmを検索し、ダウンロードしたJcode-2.0*.tar.gzを解凍し、Jcode.pmとJcodeフォルダをライブラリフォルダに入れておく。jcode.plが./libフォルダ内にあったなら、そこに入れておく。

元のCGIで
require './lib/jcode.pl';
という部分があったら
use lib './lib';
use Jcode;
と変更する。条件分岐する場合は
use lib './lib';
if ($use_jcodepm) {
    eval 'use Jcode;';
} else {
    require 'jacode.pl';
}
などとする必要がある。useはコンパイル時に実行されるので、if文の中に直接use Jcode;を書くと、if文の条件にかかわらずuse Jcode;が実行されてしまうからだ。

そしてjcode::またはjcode::となっている部分をすべてJcode::に変更する。
counter:8,444
PHP Perl 2016年12月21日 14:35   編集
perlで書いていたCGIを、PHPしか動かないサーバーで使う必要に迫られ、PHPに書き換えた。その際の注意すべき点をまとめた。

変数

perlの場合は、変数のタイプによって頭につける文字が違う。スカラー変数は$、配列は@、ハッシュは%というように。一方PHPの場合は、すべて$をつけることになるので、一見しただけでは変数に何が保存されているのかわかりにくいという問題がある。
また、perlでは、変数のタイプが違うと、同じ名前をつけることができる。たとえば、$data@data%dataを同時に別の変数として扱える。PHPではこれはできないので、perlからPHPへの移行の際には、まずこれに注意が必要だ。

perlからPHPへ書き換える場合、変数名はそのまま使うという場合は、変数のタイプによって命名のルールを作っておくといいかもしれない。たとえば配列を入れる変数の場合は、変数名末尾に_listをつけ、ハッシュの場合は末尾に_hashをつけるとか。$data $data_list $data_hash

PHPへの書き換えの前、配列やハッシュの変数名をこのルールに基づいて変換しておくと、あとでスカラー変数と配列変数がごっちゃになって大変なことにならずに済むかもしれない。
これと関連して、PHPではperlのmy宣言のような、ローカル使用限定の宣言というものがない。
このため、後で使用したい変数名をありがちなものにしてしまうと、どこかで別の用途に同じ変数名を使ってしまって消えたり値が変わってしまうというケースもある。こうしたことからも、変数名はきちんと管理しておく必要がある。

perlからPHPへ

perl php
ファイルに値が存在するかどうか if ($data) { if (isset($data) && $data) {
perlからPHPへ移行する際、一番多く使うのがこのissetかもしれない。
PHPでは、未定義の変数をいきなり参照しようとするとエラーになる。
このため、未定義の値を参照しようとする場合は、定義済みかどうかをチェックするissetを使って
isset($data)
を追加しておく必要がある。
これは配列や連想配列の場合でも同じで、連想配列$data_hashは定義済みでも、$data_hash['sheck']が未定義だとエラーになる。
配列や連想配列を参照する場合も
if (isset($data_hash['sheck']) && $data_hash['sheck']) {
などとする必要がある場合がある。
perl php
文字列はクォーテーションで囲む qq() ""
perlでもダブルクォーテーションは使えるが、HTMLの記述などで文字列内にダブルクォーテーションがある場合、エスケープするのが面倒なので、ウチではqq()を使う場合が多いが、PHPでは使用できない。
このため、文字列は基本的にダブルクォーテーションで囲って、中のダブルクォーテーションは地道にエスケープするしかない。
サクラエディタの正規表現を使った置換で一気に変換しようとしても、うまくいかない。
qq()から""への変換ぐらいはできるが、qq()内のダブルクォーテーションのエスケープが難しい。
サクラエディタの置換で何とかなりそうなのは、以下。
サクラエディタの「置換」のオプションで「正規表現」をチェックして使用する
置換前置換後
my除去 (\s*)my\s+([\$@%]) $1$2
ハッシュから連想配列へ (\$[^ $!(){]+?)\{(.+?)\} $1[$2]
行末のifを行頭へ (\s*)((.*)?) if (.+); $1if ($4) { $2; }
subをfunctionに ^sub ([^ ]+) function $1()
正規表現のマッチを変換 (\$[^&\|\=]+) =~ (\/(.*)?\/) preg_match(\'/$3/\',$1)
正規表現の置換を変換(複数回) (\$.+) =~ s(\/(.*)?\/)([^\/]*)\/i?m?s?g $1 = preg_replace(\'$2\',\'$4\',$1)
正規表現の置換を変換(1回のみ) (\$.+) =~ s(\/(.*)?\/)([^\/]*)\/i?m?s?[^g] $1 = preg_replace(\'$2\',\'$4\',$1,1)
関数の呼び出し変更 (^[^\/]*)&(?!amp;|lt;|gt;|quot;|nbsp;)([^ &\(\);="+#]+)(\Plugin not found.?([^\/]*$) $1$2($4)$5

さらにPerlとPHPの違いは...
perl php
ハッシュを配列変数に $in{'data'} $in['data']
elsif else if
ファイル、ディレクトリが存在するかどうか -e $path file_exists($path)
ファイルかどうか -f $path is_file($path)
ディレクトリかどうか -d $path is_dir($path)
ループをスキップ next; continue;
ループを終了 last; break;
配列を初期化 @data = (); $data = array();
ファイルの更新時間 (stat($path))[9] filemtime($path)
ファイルのサイズ (stat($path))[7] filesize($path)
ファイルを開いて内容を配列に保存 open(DATA,$path);
@data = <DATA>;
close(DATA);
$data_list = file($path);
ファイルを開いて中身を読み込む作業はPHPでは、簡単に書ける。オプションでFILE_IGNORE_NEW_LINESをつけると行末の改行文字が削除され、FILE_SKIP_EMPTY_LINESをつけると、空の行を読み込まない。
perl php
ファイルに書き込む open(DAT,">$path");
print DAT @data;
close(DAT);
file_put_contents($path, $data_list, LOCK_EX);
ファイルへの書き込みも、PHPでは簡単に書ける。LOCK_EXは書き込み中にファイルをロックするオプション。
perl php
配列から重複した要素を削除 %seen = ();
@data = grep { ! $seen{$_} ++ } @data;
$data_list = array_unique($data_list);
これは1行でできる便利な関数がある。
perl php
文字列を区切り文字で区切って配列にする split(/<>/,$data) explode('<>',$data)
文字列を区切り文字で区切って配列にする split(/^\d{4}$/,$data) preg_split('/^\d{4}$/',$data)
普通に文字列で分割する場合はexplodeを使うらしい。
preg_splitでもできるが、正規表現を使い、処理が少し遅いらしいので、正規表現が必要ない場合はexplodeの方がいいということだ。PHPで正規表現を使う場合は、
//で囲まれたパターンをさらに''(シングルクォーテーション)で囲ってやる必要がある。
perl php
複数の文字列を指定文字を介して連結 $str = join('<>',$str1,$str2); $str = join('<>',array($str1,$str2));
ほぼ同じだが、PHPの場合は第2引数として明示的に配列を指定する必要がある。
perl php
配列の各要素について処理 foreach $data (@data) { foreach ($data_list as $data) {
as と続けて配列から取り出す要素名を指定する。perlでは$dataを省略すると$_に格納されるが、PHPではas以降は省略できないようだ。そして、配列と要素で同じ変数名は使えないので、配列を保存した変数は$data_listなどと別の名前にしておく必要がある。
比較演算子
== → == か === (===は値が同じで、なおかつ型が同じかどうか判定。==は型を相互変換した上で値が同じか判定)
!= != か !== (!==は値が等しくないか、型が違う場合にTrue。!=は型を相互変換した上で値が違う場合True)
eq ===
ne !==
perl php
ハッシュの各キーについて処理 foreach $key (keys %data) foreach (array_keys($data_hash) as $key)
PHPの場合、array_keysでキーを取得できる。
foreach ($data_hash as $_) {
とすると、$_はキーではなく、値になる。キーと値を同時に処理したい場合は、
foreach ($data_hash as $key => $value) {
とすると、$keyにキーが、$valueに値が入る。
perl php
文字列が含まれるかどうか if ($str =~ /str/) { if (strpos($str,'str')) {if (strstr($str,'str')) {
文字列が含まれるかどうか(正規表現) if ($str =~ /\d{1,3}$/) { if preg_match('/\d{1,3}$/',$str) {
文字列が含まれるかどうか
(正規表現、マッチ部分を後で使用)
$str =~ /[a-z]+(\d{1,3}$)/;
$dig = $1;
preg_match('/[a-z]+(\d{1,3}$)/',$str,$match);
$dig = $match[1];
指定した文字列が含まれるかどうかの判定だが、これも単純な文字列ならstrposやstrstrの方が高速、正規表現を使うならpreg_matchということらしい。
マッチした部分を後で使用する場合は、preg_matchに第3引数を指定すると、マッチ部分が配列に格納される。
パターン全体にマッチするのが$match[0]、最初の()内にマッチするのが、$match[1]ということになる。
perl php
文字列の置換 $str =~ s/</&lt;/g; $str = str_replace('<','&lt;',$str);
文字列の置換 $str =~ s/(^[a-z]{4})/~$1~/g; $str = preg_replace('/(^[a-z]{4})/','~$1~',$str);
正規表現を使わない場合は、str_replaceの方が高速らしい。
正規表現を使う場合は、perlの場合と同じように、パターン全体にマッチした部分が$0、最初の()内にマッチした部分が$1、2番目の()内にマッチした部分が$2に格納される。
perl php
配列に要素を追加 push(@new_data,$new_data); array_push($new_data_list,$new_data);
$new_data_list[] = $new_data;
配列に要素を追加するのはarray_pushを使うが、
$new_data_list[] = $new_data;
のようにした方が高速らしい。
エポック時間から日付、時間を得る ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)=localtime(time);
$month = $mon+1;
$year += 1900;
$tm = sprintf("%04d年%02d月%02d日%02d:%02d", $year, $month, $mday, $hour, $min);
$tm = date("Y年m月d日 H:i",time());
これはPHPの方が圧倒的に簡単。dateを使えば、月に1足したり、年に1900足したりする必要なく、フォーマット済みの日付が得られる。
perl php
日付からエポック時間を得る use Time::Local 'timelocal';
$epc = timelocal($sec, $min, $hour, $mday, ($month - 1), ($year - 1900));
$epc = mktime($sec, $min, $hour, $mday, $month, $year);
これもPHPの方が簡単。Time::Localモジュールを宣言する必要もなく、月から1引いたり、年から1900引く必要もない。

...と長々と説明したが、このような変更をひとつずつやるのが面倒ということで書いたのが
PHP/PerlからPHPへ書き換え補助PHP
counter:4,554