Perl で、1GB のデータ(テキストファイル)を作成する方法
#!/usr/bin/perl
open(OUT, '>sample.txt');
for(my $i = 0; $i < 2^30; $i++) { print OUT 'a'; }
close(OUT);
1;
</code>
#!/usr/bin/perl
open(OUT, '>sample.txt');
for(my $i = 0; $i < 2^30; $i++) { print OUT 'a'; }
close(OUT);
1;
</code>
<?php
$dir_path = 'your_directory';
$filename = array();
$dir = dir($dir_path);
while($file = $dir->read()) {
if(!ereg('(^\.$)|(^\.\.$)', $file)) {
$filename[] = $file;
}
}
$dir->close();
?>
メールに添付されてきた .pdf ファイルが巨大すぎてメールサーバによって分割されて自分のところに配信された結果、Base64 のデータがバラバラに届いたケースを考える。
このような場合は Perl が活躍する。まず、バラバラになった Base64 のファイルは仕方がないのでエディタを使って手作業でくっつけて 1つのファイルにする。そのあと、以下のコマンドを実行すればよい。
使用するモジュール
use MIME::Base64;
perl -MMIME::Base64 -ne 'print decode_base64($_)'
< くっつけたBase64ファイル名.txt > 出力ファイル名.pdf
(上は、すべて1行に書く)
MIME::Base64 - base64文字列のエンコードとデコード (perldoc.jp)
配列の中にある重複するレコードを削除する場合、Perl なのでいろいろな方法があると思うが、以下のコードを試してみて欲しい。
#/usr/bin/perl -w
my @array = ('a', 'b', 'c', 'a');
my %count;
@array = grep {!$count{$_}++} @array;
print @array; # 出力は abc となる。
1;
以下のように、replace.conf のハッシュ変数に置換前・置換後の文字列を書いておき、replace.pl を実行する。
replace.conf の内容
#/usr/bin/perl -w
use strict;
our $HOME_DIR = '/home/youraccount';
our $DIR = "$HOME_DIR/targetdirectory";
our %WORDS = (
'ブラウザー' # 置換前の文字列
=> 'ブラウザ', # 置換後の文字列
'FTP' # 置換前の文字列
=> 'FTP', # 置換後の文字列
# ... 以下、
'置換前の文字列1'
=> '置換後の文字列1',
'置換前の文字列2'
=> '置換後の文字列2'
# ... という形で追加していけばよい。
);
1;
replace.pl の内容
#!/usr/bin/perl
require './jcode.pl';
require './replace.conf';
opendir(DIR, $DIR) or die "Couldn't open $DIR directory.";
my @files = readdir(DIR);
closedir(DIR);
foreach my $file (@files) {
if(!(-d $file) && $file ne '.' && $file ne '..' && $file ne __FILE__) {
print "$file\n";
replace($file);
}
}
sub replace {
my ($file) = @_;
open(IN, "$DIR/$file") or die die "Couldn't open $file.";
my @lines = <IN>;
close(IN);
open(OUT, ">$DIR/$file.tmp") or die die "Couldn't open $file.";
foreach my $line (@lines) {
&jcode'h2z_sjis(\$line);
while(my($before, $after) = each %WORDS) {
if($line =~ /$before/) {
$line =~ s/$before/$after/g;
}
}
print OUT $line;
}
close(OUT);
rename("$DIR/$file.tmp", "$DIR/$file");
unlink("$DIR/$file.tmp");
}
1;
Perl で、改行で区切られたファイルを読み込んで、1行ずつ処理するには、while() {...} のように書けばよい。
#!/usr/bin/perl
# 読み込むファイル名をここに書く
our $INPUT_FILE = 'yourfile.txt';
# ファイルをオープンする
open(IN, $INPUT_FILE) || die("Can't open a file: $!");
# 上の open で与えられた IN で、 というように書き、while 文中で処理できる
while() {
# 1行ずつ表示する
print $_; # 現在処理中の 1行は $_ で表わす。
}
# ファイルをクローズする
close(IN);
また、 の内容を 1行ずつ細かく処理するには、@IN = ; とするやり方も使える。以下はイテレータのループを while ではなく foreach で処理する例。
#!/usr/bin/perl
# 読み込むファイル名をここに書く
our $INPU_FILE = 'yourfile.txt';
# ファイルをオープンする
open(IN, $INPUT_FILE) || die("Can't open a file: $!");
# 上の open で与えられた IN で、 というように書き、while 文中で処理する
@IN = ;
# $INPUT_FILE の内容は @IN に入ったので、ここでファイルをクローズできる
close(IN);
foreach my $line (@IN) {
# 1行ずつ表示する
print $line; # 現在処理中の 1行は $line で表わす。
}
gmap_excel.html のファイルの内容
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="styles-site.css" type="text/css" />
</head>
<body>
<form enctype="multipart/form-data" action="gmap_excel.cgi" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="2000000" />
<table width="600">
<tr><td>Names file:</td><td><input type="file" name="file" />
<input type="submit" value="Upload" /></td></tr>
</table>
</form>
</body>
gmap_excel.cgi のファイルの内容
#!/usr/bin/perl
use lib "path_to_your_perl_lib/lib/perl5/site_perl";
use strict;
use Geo::Google;
use CGI;
our $XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; # XML ヘッダ
our $HOME_DIR = 'your_home_dir';
our $LOCK_DIR = 'path_to_lock_dir/lock'; # do chmod 777 path_to_lock_dir/lock
our $LOCK_FILE = 'lock';
our $XML_FILE = 'path_to_your_output_xml/gmap_excel.xml'; # 出力用 XML ファイル名
our $col_index = 0; # Excel シートの現在の列(カラム)インデックス
our $row_index = 0; # Excel シートの現在の行インデックス
our @data = (); # 出力用データ
our @current_data = (); # 現在のデータ
# オブジェクト初期化
our $CGI = new CGI;
our $GEO = new Geo::Google;
# HTML 出力
print qq|Content-type: text/html\n\n|;
print qq||;
print qq||;
print qq||;
init();
print qq|
|;
# 個々のデータのハンドラ
sub dataHandler {
my ($expat, $text) = @_;
# $row_index = 0 と 1 は、Excel で データのヘッダのため必要なし
if($text !~ /^\s+$/ && $row_index > 1) { # 空白行でなければ
$current_data[$col_index] = $text;
}
}
# 要素の開始
sub startElement {
my ($expat, $tag, %attr) = @_;
if($tag eq 'Row') { # <Row>で始まるとき
$col_index = 0; # カラムを初期化
$row_index++; # 行を進める
@current_data = (); # 現在の行データを初期化
}
if($tag eq 'Data') { # <Data>で始まるとき
$col_index++; # 列を進める
}
}
# 要素の終了
sub endElement {
my ($expat, $tag) = @_;
my ($xml, $html);
my ($lng, $lat, $name, $link, $category1, $category2);
my ($street, $city, $state, $phone, $comment1, $comment2);
# </Row> のとき
if($tag eq 'Row') {
push(@data, { # データに現在の行データを代入
'category1' => $current_data[1],
'category2' => $current_data[2],
'name' => $current_data[3],
'address' => $current_data[4],
'phone' => $current_data[5],
'link' => $current_data[6],
'comment1' => $current_data[7],
'comment2' => $current_data[8]
});
}
# </Workbook> のとき
if($tag eq 'Workbook') {
$row_index = -1; # 行数を初期化 ($row_index = 0 はヘッダのため -1 からスタート)
foreach my $row (@data) {
$row_index++;
if($row->{address}) {
$lng = ''; $lat = '';
my ($degree) = getDegrees($row->{address});
if($degree) {
$lng = $degree->longitude;
$lat = $degree->latitude;
}
}
$name = $row->{name};
$link = $row->{link}
=~ /http:\/\/[!#-9A-~]+\.+[a-z0-9]/ ? $row->{link} : '';
$category1 = $row->{category1};
$category2 = $row->{category2};
($street, $city, $state) = split(',', $row->{address});
$street = $street;
$city = $city;
$state = $state;
$phone = $row->{phone};
$comment1 = $row->{comment1} eq '-' ? '' : $row->{comment1};
$comment2 = $row->{comment2};
# lng と lat が検索できたら (データが入っているということだから)
if($lng && $lat) {
$xml .= "\t<marker\n"
. "\t\tlng = \"$lng\" lat = \"$lat\"\n"
. "\t\tname = \"" . $name . "\"\n"
. "\t\tlink = \"" . $link . "\"\n"
. "\t\tcategory1 = \"" . $category1 . "\"\n"
. "\t\tcategory2 = \"" . $category2 . "\"\n"
. "\t\taddress1 = \"$street\"\n"
. "\t\taddress2 = \"$city, $state\"\n"
. "\t\tphone = \"$phone\"\n"
. "\t\tcomment1 = \"$comment1\"\n"
. "\t\tcomment2 = \"$comment2\"\n"
. "\t/>\n\n";
} elsif($row_index) {
$html .= '<tr><td>'
. "$row_index / $name / $street, $city, $state"
. '</td></tr>';
}
}
$xml = $XML_HEADER
. "<markers>\n"
. $xml
. "</markers>\n";
if($html) { # エラーがあれば
$html = '<table>'
. '<tr><td>***** 以下、緯度経度が検索不能でした。</td></tr>'
. $html
. '</table>';
} else { # エラーがなければ
print '<br />done.';
}
# ファイルのオープン
&file_lock; # ファイルロック
open(XML, ">$HOME_DIR$XML_FILE")
or die('Cannot open an XML file: ' . "$HOME_DIR$XML_FILE");
print XML $xml; # 書き込み
close(XML); # ファイルを閉じる
&file_unlock; # ロック解除
print $html;
}
}
sub getDegrees { # 住所から緯度経度を検索
return $GEO->location( address => shift); # shift = address / リファレンスを返す
}
sub init() {
# XMLパーサー作成 ('UTF-8')
my $xml_parser = new XML::Parser( ProtocolEncoding => 'UTF-8',
Handlers=>{ Start=>\&startElement,
End =>\&endElement,
Char =>\&dataHandler });
# ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
my $xml = $CGI->param('file');
# XMLパース処理
$xml_parser->parse($xml) or die "XML error: $xml_parser";
# ファイルクローズ
close($xml);
}
sub file_lock {
my $wait = 5;
while (!symlink('.',"$HOME_DIR$LOCK_DIR/.$LOCK_FILE")) {
if (--$wait <= 0) {
# &error( '現在他の方が使用中です。',
# 'しばらくしてから再度ご利用ください。');
}
sleep (1);
}
}
sub file_unlock {
unlink ("$HOME_DIR$LOCK_DIR/.$LOCK_FILE");
}
1;
gmap_excel.xml 出力例
<?xml version="1.0" encoding="UTF-8"?>
<marker
lng = "-122.125690" lat = "37.413570"
name = "Tofu House"
link = ""
category1 = "Restaurant"
category2 = "Korean"
address1 = "4127 El Camino Real"
address2 = " Palo Alto, CA 94306"
phone = "650-424-8805"
comment1 = ""
comment2 = "Santa Clara の Tofu House と同じ店。"
/>
...
Perl の場合は、CGI として動作させる場合は、必ず
print qq|Content-type: text/html\n\n|;
という行が必要な以外は、PHP とほとんど同じである。 アップロードには、いつも使っているおなじみの CGI モジュールを使う。
use CGI;
そうすると、以下のようにアクセスできる。
our $CGI = new CGI;
...
my $xml = $CGI->param('file');
$xml はファイルポインタが入っている。通常は open(IN, "..."); などするときの IN にあたるものである。以下は、コード。アップロードする側の HTML ファイルは PHP でアップロードファイルを処理するで紹介したのと同じ。
#!/usr/bin/perl
use strict;
use CGI;
our $CGI = new CGI;
print qq|Content-type: text/html\n\n|;
init();
sub init() {
# XMLパーサー作成 ('UTF-8')
my $xml_parser = new XML::Parser( ProtocolEncoding => 'UTF-8',
Handlers=>{ Start=>\&startElement,
End =>\&endElement,
Char =>\&dataHandler });
# ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
my $xml = $CGI->param('file');
# XMLパース処理
$xml_parser->parse($xml) or die "XML error: $xml_parser";
# ファイルクローズ
close($xml);
}
1;
ファイルのロックアンロックは、flock 関数を使う方法もあるが、この方法はすべてのプラットフォームで使えるというわけではない。 そのため、OS のシンボリックリンク機能を使う。OS がファイルを作成するときに排他制御を行うのを利用する。空のファイルを作成し、そのファイルが存在するかどうかチェックする方法と一見変わらないように見えるが、この方法だとファイルをオープンし、クローズするという2ステップが必要となる。このため、完全な排他にはならない。よって、シンボリックリンクというところがミソである。シンボリックリンクの他には、ディレクトリを作成する方法、ファイル名を変更する方法もあるが、私が知る限りはこの方法を紹介しているサイトが多いように思う。
使用するモジュール
なし
#!/usr/bin/perl
our $HOME_DIR = 'ホームディレクトリをここに書く';
our $LOCK_DIR = "$HOME_DIR/lock"; # このディレクトリのアクセス権は 777 である。
our $LOCK_FILE = 'ロックするファイル名をここに書く。なんでもよい。';
sub file_lock {
my $wait = 5;
while (!symlink(".","$HOME_DIR/$LOCK_DIR/.$LOCK_FILE")) {
if (--$wait <= 0) {
# &error('現在他の方が使用中です。しばらくしてから再度ご利用ください。');
}
sleep (1);
}
}
sub file_unlock {
unlink ("$HOME_DIR/$LOCK_DIR/lock/.$LOCK_FILE");
}
1;