綾小路龍之介の素人思考

[perl] 1 行スクリプト覚書 with Active Perl

ここでは perl の 1 行スクリプトを使用し、単一の機能をどれだけ短いスクリプトで表現できるかを模索していきます。Windows のコマンドラインが非力なのは仕方ありません。GUI が売り物の Windows ですから。unix ほどの高機能なシェルはいらないけど、unix 使いに負けないくらいの仕事がしたい貴方に。単一の機能の集合がプログラムで、短い行で様々な機能を実現できればプログラムを書くのも楽になると思います。単一の機能を極限まで短くすることには、Perl の美学に通ずるものがあると思います。言い換えればここは、Perl の隅を針で突く、プロジェクトページです。

目次

この稿を読む前に

この稿はWindowsMe上でActivePerlを走らせたい人向けの情報です。しかし、シェルとしては非力なWindowsMeのcommandプロンプトでの結果を載せているため、多分Unix版のPerl上でも動くでしょう。perlの前についている>マークはWindowsのプロンプトマークです。今から行うのはActivePerlでの話なのでunixのBシェルやCシェルのプロンプトマークである$や%は使いません。

ActivePerlで1行スクリプトを書く場合に気をつけること

unix版のPerlとWindows版のActivePerlの違いと言うよりもシェルの違いと言った方が良いのかもしれない。世にある様々な1行スクリプトの多くはunix版のPerlで動くように作られていると思う。ただ、unixはインストールするのめんどくさいしとか、Cygwinほどリッチな環境もイラナイしと思う輩のために、ActivePerlはある。指し当たって注意することはたった一つ。それは、Windiwsのcommandプロンプトではダブルコーテーションが1組しか使えないと言うことだ。だからWindowsMeとActivePerlの環境の元では下のように書くと多分期待どおりの結果を返さない。(と言うか僕はWindows野郎でMe使いなので他の環境についてよく知らない。)

> perl -e "print "Hello World!!";"

原因は先に述べたようにダブルコーテーションが1組しか使えない点にある。Hello World!!と表示させたい場合は下のように書く。忘れてはならないことは-eの後に続くプログラム文はダブルコーテーションで全てを括らねばならないと言う点だ。シングルコーテーションで括ってprint文の引数をダブルコーテーションで括るのもご法度である。

> perl -e "print 'Hello World!!';"

[文字出力] 文字列を標準出力に表示する(変数にセットされた)

D:\>perl -e "$xx = 'ww'; print \"$xx\";"
ww
D:\>

変数を使いたいときもあるかもしれない。そんな場合はシングルコーテーション(')をエンマークダブルコーテーション(\")のようにするといいんだな。これでも全く同様な出力が得られるんだな。この場合、コンパイラは下のように理解しているんだな。

$xx = 'ww';
print "$xx";

ダブルコーテーションで囲まれているので、変数展開が行われたんだな。これさえ覚えれば出力は完璧なんだな。

[文字出力] 文字列+改行を標準出力に表示する(メタ文字)

D:\>perl -e "print \"Hello World\n\";"
Hello World
D:\>

lオプションを使って改行するより、わかりやすいコマンドになっているんだな。つまり、通常のprint文で使用するダブルコーテーションを円マークでエスケープしてるところがミソ。これをPerlは下のように解釈しているんだな。

print "Hello World\n";

[文字出力] 文字列+改行を標準出力に表示する(lオプション)

C:\>perl -le "print "'ww'";"
ww
C:\>

つまりはechoコマンドのエミュレートということなんだな。まぁこのくらいならわざわざPerlを使うこともないんだけど、上で改行なしの表示法をやったし、何かと改行はあったほうが良いということで紹介しておこう。lオプションでprint文の最後に必ず改行コードを付けるようになるんだな。注意することはこのオプションを-elとしないこと。ちなみにそうすると下のようになる。-eオプションはこれ以降をPerlスクリプトとして解釈するようだけど、多分-e以降に-lが含まれていると理解されたんだろうな。

C:\>perl -el "print "'ww'";"
C:\>

[文字出力] 文字列+改行を標準出力に表示する(メタ文字,変数)

D:\>perl -e "$xx = 'ww'; print \"yy$xx\n\";"
yyww
D:\>

前述の様に、コマンドプロンプト上から入力されたエンマークダブルコーテーションの間に変数やメタ文字が含まれると、コンパイラはこれを解釈するので、下のようになるんだな。

$xx = 'ww';
print "yy$xx\n";

ここまでくれば文字列の出力は完璧なんだな。

[時刻表示] 時刻表示にまつわるエトセトラ

D:\>perl -e "($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);$year+=1900;$mon+=1; print \"$year/$mon\""
2005/10
D:\>

WindowsMEには時刻表示機能がないの何でだろー。

D:\>perl -le "@D=localtime(time); $D[5]+=1900; $D[4]+=1; foreach (@D){print}"
41
49
22
30
10
2005
0
302
0
D:\>

覚えてしまえlocaltimeで渡される配列。さすれば、あーんなこともこーんな事も自由自在だったりする。

D:\>perl -e "@D=localtime(time); $D[5]+=1900; $D[4]+=1; print \"$D[5]/$D[4]/$D[3] $D[2]:$D[1]:$D[0]\""
2005/10/30 22:52:34
D:\>

localtimeの引数を省略するとtimeを引数に取ったことと同じになるので、下のようにしても同じ。短さ追求ならこちらのほうがいいかな。

D:\>perl -e "@D=localtime; $D[5]+=1900; $D[4]+=1; print \"$D[5]/$D[4]/$D[3] $D[2]:$D[1]:$D[0]\""
2005/10/30 22:52:34
D:\>

短さを追求してわかりやすさを捨てた結果。

D:\>perl -e "@D=localtime; print 1900+$D[5].\"/\".$D[4]++.\"/$D[3] $D[2]:$D[1]:$D[0]\""
2005/9/30 23:44:59
D:\>

あんまり美しくはないけれど。というかやめたほうがいいかも。上よりも長いし、出力内容見にくいし。

D:\>perl -le "@D[0..5]=localtime(time); $D[5]+=1900; $D[4]+=1; $\"='/'; @D=reverse @D; print \"@D[0..5]\";"
2005/10/30/23/7/1
D:\>

遊んでみるとこうなる。1秒ごとに時間を出力。意味はない。

D:\>perl -le "while(1){@D=localtime; print 1900+$D[5].\"/\".$D[4]++.\"/$D[3] $D[2]:$D[1]:$D[0]\"; sleep(1);}"
2005/9/31 0:3:28
2005/9/31 0:3:28
2005/9/31 0:3:29
2005/9/31 0:3:30
2005/9/31 0:3:31
2005/9/31 0:3:33
2005/9/31 0:3:33
2005/9/31 0:3:34
Terminating on signal SIGINT(2)
D:\>

おばかな応用として、ラーメンタイマー改にしてみる。

D:\>perl -le "for(1..6){@D=localtime; print 1900+$D[5].\"/\".$D[4]++.\"/$D[3] $D[2]:$D[1]:$D[0]\a\"; sleep(30);} print\"\a\a\a\";"
2005/9/31 0:9:17
2005/9/31 0:9:47
2005/9/31 0:10:17
2005/9/31 0:10:47
2005/9/31 0:11:17
2005/9/31 0:11:47
D:\>

localtimeを文字列で評価すると人間にもわかる(つまり何時何分何秒)という形式で表示される。

$file_inf=localtime((stat($file))[9]);

[ディレクトリ走査] カレントディレクトリ中のファイルとディレクトリを表示する

D:\>perl -e "print <*>"

[ディレクトリ走査] カレントディレクトリ中のファイルとディレクトリの情報表示(dir /V)

このくらいならdirたたいたほうが速いって?そのとおり。ネタがなくなったって?そのとおり。

C:\>perl -e "while(<*>){print $_.\"\t\".scalar(localtime((stat($_))[9])).\"\n\";}"
a.bat   Tue Feb 14 02:35:46 2006
a.pl    Tue Feb 14 02:35:22 2006
a.txt   Tue Feb 14 02:02:04 2006
C:\>

しつこくやるのが上達のススメ。ということでファイルサイズを付加してみた。だってdir /Vだと1行に収まらないんだもん。

C:\>perl -e "while(<*>){@F=stat($_); print join\"\t\",($_,$F[7],scalar localtime $F[9],\"\n\");}"
a.bat   1474    Tue Feb 14 02:35:46 2006
a.pl    4988    Tue Feb 14 02:35:22 2006
a.txt   4844    Tue Feb 14 02:02:04 2006
C:\>

[ディレクトリ走査] カレントディレクトリ中のファイルのみを表示する

perlでファイルだけを表示してみる。ポイントはファイルテスト演算子を使ってファイルかディレクトリかをチェックするということ。ディレクトリ以外をファイルと思って処理してみる。昔書いていたものでは不十分とわかったので。

D:\>perl -e "print <*.*>"
$ perl -le 'print join"\n", grep{! -d $_}<*>'

文字コード変換

C:\>perl -e "require \"jcode.pl\"; print \"失敗\"; "
失敗
C:\>perl -e "require \"jcode.pl\"; $s=\"失敗\"; jcode::convert(\$s,'euc');print $s;"
シコヌヤ
C:\>

文字列だけならこんな感じ。ファイル内容を変換する場合は下のような感じ

C:\>perl -pe "require \"jcode.pl\"; jcode::convert(\$_,'euc'); " test.txt>test2.txt
C:\>

でも半角カタカナは上手く変換できないんだな。デスクトップ上に半角カナの名前があることのほうが問題か?

単語抜き出し

I have a dream that one day this nation will rise up and live out the true meaning of its creed: "We hold these truths to be self-evident: that all men are created equal." I have a dream that one day on the red hills of Georgia the sons of former slaves and the sons of former slaveowners will be able to sit down together at a table of brotherhood. I have a dream that one day even the state of Mississippi, a desert state, sweltering with the heat of injustice and oppression, will be transformed into an oasis of freedom and justice. I have a dream that my four children will one day live in a nation where they will not be judged by the color of their skin but by the content of their character. I have a dream today.

C:\>perl -anle "for(@F){push @d,$_} END{@e=grep(!$tmp{$_}++,@d); print \"@e\"}" a.txt
I have a dream that one day this nation will rise up and live out the true meani
ng of its creed: "We hold these truths to be self-evident: all men are created e
qual." on red hills Georgia sons former slaves slaveowners able sit down togethe
r at table brotherhood. even state Mississippi, desert state, sweltering with he
at injustice oppression, transformed into an oasis freedom justice. my four chil
dren in where they not judged by color their skin but content character. today.
C:\>

空ファイルを作るにはどーすりゃいーんじゃコラ

普通なら下のような感じで書けば、ファイル名1.txt,2.txt,3.txt,4.txt,5.txt,6.txt,7.txt,8.txt,9.txt,10.txtのファイル中にファイル名が書き込まれたファイルができるんだな。

for(1..10){
  $f="$_.txt";
  open(OUT,">$f");
  print OUT "$f";
  close OUT;
}

そこでこいつを下のようにしてみるんだな。

C:\>perl -e " for(1..10){$f=\"$_.txt\"; open(OUT,\">$f\"); print OUT \"$f\"; close OUT; }"
ファイルを作れませんでした.
C:\>

これはActivePerlの仕様なのか?もう今日は寝る。引数にワイルドカードも取れないし。どーすりゃいーんだ。

C:\>perl -e "for(1..10){print $_}"
12345678910
C:\>perl -e "for(1..10){print $_}">test.txt
C:\>perl -pe "" test.txt
12345678910
C:\>
C:\>perl -e "for(1..10){ system \"copy test.txt test$_.txt\"}"
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
        1 個のファイルをコピーしました.
C:\>

マッチしたファイルの内から特定の文字列のある行を抜き出す

D:\>perl -e "@F=<*.html>; foreach(@F){open IN,$_; print \"----------$_\n\"; while(<IN>){print if(m/1/);}; close IN;}"
----------test.html
    4 spaces 1 space
                2 tabs  1 tab
----------test2.html
<h1>PerlTestBody</h1>
D:\>

例えばこの例では"1"という文字が含まれている行を抜き出しているんだな。m//の中身を特定の文字に変えればいいんだな。

D:\>perl -e "@F=<*.html>; foreach(@F){open IN,$_; print \"----------$_\n\"; while(<IN>){print if(m/.*/);}; close IN;}"
----------test.html
    4 spaces 1 space
                2 tabs  1 tab
----------test2.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner,SearchOn<a href="http://www.google.com">Google</a>WithSomeWords.Google'sLogo<img href="http://www.google.com" alt="GoogleLogo">IsHere.</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>

ファイル名を変更する

D:\>perl -e "opendir (DIR,'.'); @fname=readdir DIR; foreach(@fname){if(m/t_0.*\.wav/){ rename ($_,asd)}}"
D:\>

この場合同じファイル名になっても無条件に(エラーメッセージを出さずに)変更してしまうんだな。だからかなり危険なんだな。

D:\>perl -le "for(<*.wav>){ m/(t_)([0-9]*)(\.wav)/; $n=sprintf(\"%04d\",$2); $n= \"$1$n$3\"; print $n;rename $_,$n;}"
t_0010.wav
t_0011.wav
t_0012.wav
t_0013.wav
t_0014.wav
t_0015.wav
t_0016.wav
t_0017.wav
D:\>

rename s/\.eps\.png/\.png/ *eps.png

[perl] ファイル中の文字を置換する

だめぽ。jperlでしか動かんのかも。いまどきjperlもないだろということで。今windowsで動くperlといえばactive perlとstrawberry perlだけど、それぞれの違いは、モジュールの導入形式。前者は独自パッケージ形式ppm、後者はcpan経由のコンパイル。つまり、strawberry perlはlinux perlライクなパッケージ管理ができる。その代わりコンパイル環境が同時にセットアップされる。ぼくはこれを「strawberry perlは新しいものが使える」と表現してしまった。

D:\>jperl -ne "tr/A-Za-z0-9()?!/A-Za-z0-9()?!/;print;" original.txt > new.txt

[perl] ファイルの内容をソートして標準出力

sortコマンドのエミュレートな感じかな。

D:\>perl -e "print sort(<>); exit;" c.txt

[perl] ファイル中の重複行を削除してソートの後標準出力

D:\>perl -ne "push(@l, $_) unless $f{$_}++; END{print sort(@l);}" c.txt > ccc.txt

ファイル中に特定の文字が含まれていたら出力(findコマンドに似てるかも)

C:\>perl -ne "if(m/失敗/){print}" Ma.18
C:\>
C:\>perl -ne "if(m/#FF0000/){print}" Magrdadd
<body bgcolor="#ffffff" text="#000000" link="#0000ff" vlink="#cc33ff" alink="#FF0000">
        <td width="10%" nowrap bgcolor="#FFCCCC" valign="top"><font color="#FF0000">すでに登録済みの<br>メールマガジン</font></td>
C:\>

このままだとMa.18やMagrdaddの内容しか走査しないので、使いにくい。というよりもgrepコマンドみたいなことがしたいんだな。

ファイル中の重複行を削除して標準出力

D:\>perl -ne "push(@l, $_) unless $f{$_}++; END{print @l;}" c.txt > ccc.txt

こーゆー奴は面白いってもんでしょ。nオプションは、スクリプトをwhile(<>)ループで囲むんだな。つまり、上のスクリプトは下のように解釈されていたんだな。

while(<>){
  push(@l, $_) unless $f{$_}++;
}
print @l;

読み込んだ行$_を配列@lにpushする際の条件として、$f{$_}が偽(0)であることを条件にしているんだな。未定義の$f{$_}が呼び出されたら、これを定義し値をインクリメント($f{$_}を1増やす)する。以前に$f{$_}が定義されていれば$f{$_}は0でないから、push(@l, $_) は実行されない。ファイル中から行を取り出せなくなったらダイヤモンド演算子は偽を返すからループから抜ける。スクリプト中のEND{}で囲まれたとこはループから抜けたら実行されので、最後に重複した要素のない配列を標準出力する。まぁどうせ後から配列操作をしないんだし、こんな感じのほうがすっきりしてていいかも。ここ中でのprintはprint $_と同義なんだな。

D:\>perl -ne "print unless $f{$_}++;" c.txt > ccc.txt

ファイルの内容に行番号を付けて標準出力

D:\>perl -ne "printf(\"%5d: %s\",$.,$_);" c.txt

なんでもないようで示唆にとんだスクリプトなんだな。最初に気をつけるのはprintf文の中身なんだな。通常のprint文ではダブルコーテーションで出力内容を括るけど、この場合はダブルコーテーションの前に円マーク(バックスラッシュ)をつけて明示的にエスケープしているんだな。多分これはWindows上で動くActivePerlだからなんだな。こうすることでスペースをわざわざ別コーテーションで括ることなしに使えるようにしているんだな。次に-nオプションでwhileループ中のスクリプトを書いているんだな。つまりこんな感じで解釈されたということなんだな。

while(<>){
  printf("%5d: %s",$.,$_);
}

printf文はダブルコーテーション中の%以降で指定した出力形式で、これに続く変数を展開して出力するんだな。2つの重要な特殊変数が使われているんだな。一つ目は$.なんだな。これは読み込み中のファイルの現在読み込んだ行の行番号を示しているんだな。これをprintf文で5桁(5桁未満の場合は空白で埋める)にしているんだな。5の部分を適当な整数に変えることができるんだな。二つ目は$_なんだな。これは読み込み中のファイルの現在読み込んだ行の内容を示しているんだな。これをprintf文で%sにて展開、つまり文字列として展開しているんだな。

簡易HTMLタグ削除フィルタ

C:\>perl -0 -pe "s/<[^>]*>//g;"
C:\>perl -e "s|<(.*?)>(.*?)</$1>|$2|m"
D:\>perl -ne "s|<(.*)>(.*)<\/\1>|$2|ig; print;" test.html
test
D:\>perl -ne "print;" test.html
<html>test</html>
D:\>
D:\>perl -ne "while(s|<(.*)>(.*)<\/\1>|$2|g) { print \"$_\n\"; }" test.html
<head><title>PerlTest</title></head><body>PerlTestBody</body>
<title>PerlTest</title>PerlTestBody
PerlTestPerlTestBody
D:\>perl -ne "print" test.html
<html><head><title>PerlTest</title></head><body>PerlTestBody</body></html>
D:\>
D:\>perl -ne "while(s|<(.*)>(.*)<\/\1>|$2|g) { }print \"$_\n\";" test.html
PerlTestPerlTestBody
D:\>perl -ne "print" test.html
<html><head><title>PerlTest</title></head><body>PerlTestBody</body></html>
D:\>
D:\>perl -ne "print;" test.html
<html><head><title>PerlTest</title></head>
<body>PerlTestBody</body>
</html>
D:\>perl -ne "while(s|<(.*)>(.*)<\/\1>|$2|g) { }print \"$_\n\";" test.html
<html>PerlTest
PerlTestBody
</html>
D:\>
D:\>perl -ne "$f.=$_; END{ while($f=~s|<(.*)>(.*)<\/\1>|$2|sg){} print \"$f\";}" test.html
PerlTest
PerlTestBody
D:\>perl -ne "print" test.html
<html><head><title>PerlTest</title></head>
<body>PerlTestBody</body>
</html>
D:\>
D:\>perl -ne "$f.=$_; END{ while($f=~s|<(.*)>(.*)<\/\1>|$2|sg){} print \"$f\";}" test.html
PerlTest
PerlTestBody
<hr>AllAboutPerlOneLiner
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
</body>
</html>
D:\>
D:\>perl -ne "$f.=$_; END{ while($f=~s|<(.*) .*>(.*)<\/\1>|$2|sg){} print \"$f\";}" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
Google
</body>
</html>
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>
D:\>perl -ne "$f.=$_; END{ while($f=~s|<(.*) .*>(.*)<\/\1>|$2|sg){} while($f=~s|<(.*)>(.*)<\/\1>|$2|sg){} print \"$f\";}" test.html
PerlTest
PerlTestBody
<hr>AllAboutPerlOneLiner
Google
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>
D:\>perl -0777 -e "$_=<>; while(s|<(.*) .*>(.*)<\/\1>|$2|sg){} while(s|<(.*)>(.*)<\/\1>|$2|sg){} print;" test.html
PerlTest
PerlTestBody
<hr>AllAboutPerlOneLiner
Google
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>
D:\>perl -0777 -e "$_=<>; while(s|<(.*?) .*?>(.*?)<\/\1>|$2|sg){} while(s|<(.*?)>(.*?)<\/\1>|$2|sg){} print;" test.html
PerlTest
PerlTestBody
<hr>AllAboutPerlOneLiner,SearchOnGoogleWithSomeWords.Google'sLogo<img href="http://www.google.com" alt="GoogleLogo">IsHere.
Google
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner,SearchOn<a href="http://www.google.com">Google</a>WithSomeWords.Google'sLogo<img href="http://www.google.com" alt="GoogleLogo">IsHere.</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>
D:\>perl -0 -pe "s/<[^>]*>//g;" test.html
PerlTest
PerlTestBody
AllAboutPerlOneLiner,SearchOnGoogleWithSomeWords.Google'sLogoIsHere.
Google
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner,SearchOn<a href="http://www.google.com">Google</a>WithSomeWords.Google'sLogo<img href="http://www.google.com" alt="GoogleLogo">IsHere.</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>
D:\>perl -0 -pe "s/<[^>]*>//g;" test.html
PerlTest
PerlTestBody
AllAboutPerlOneLiner,SearchOnGoogleWithSomeWords.
Google'sLogoIsHere.
Google
D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner,SearchOn<a href="http://www.google.com">Google</a>WithSomeWords.
Google'sLogo<img href="http://www.google.com" alt="GoogleLogo">IsHere.</p>
<a href="http://www.google.com">Google</a>
</body>
</html>
D:\>

簡易と銘打っているならやっぱりスクリプトが長いのはよろしくないなぁということで。最初にあげたものに帰着したんだな。わざわざ-0をつけているのは1ファイルの内容をNULL値まで一気に$_に代入ということなんだけど、短い割りには上手いことHTMLタグを処理しているんだな。コンパイラはこれを下のように解釈しているんだな。

while (<>) {
  s/<[^>]*>//g;
} continue {
  print;
}

つまり、<で始まって、>以外の文字列が続いて、>で終わっているところは全て削除という操作をしているんだな。だから下手すると、意図しないところでマッチングされてしまうこともあるんだな。間違って<や>を文中で使用してしまうとそこも削除されてしまう可能性があるということ。

[フィルタ]各行のテキストに何かを追加して出力

C:\>perl -lne "print 'document.write('.chr(0x22).$_.'<>'.chr(0x22).'+'.$_.'+'.chr(0x22).'<br>\n'.chr(0x22).');'" a.txt

[フィルタ] テキストからHTML

秀逸なのはmap{}なんだな。map{}で@Aの全要素に対して置換コマンドを実行しているんだな。map{}は戻り値が配列になるから戻り値を@Aに代入してはいけないんだな。ちなみに戻り値には@Aの各要素に含まれる改行の数が含まれているんだな。

C:\>perl -e "@A=<>; map{s/\n/<br>\n/g}@A; print @A" a.txt
I have a dream that one day this nation <br>
will rise up and live out the true meani<br>
ng of its creed: "We hold these truths t<br>
o be self-evident: all men are created e<br>
qual." on red hills Georgia sons former <br>
slaves slaveowners able sit down togethe<br>
r at table brotherhood. even state Missi<br>
ssippi, desert state, sweltering with he<br>
at injustice oppression, transformed int<br>
o an oasis freedom justice. my four chil<br>
dren in where they not judged by color t<br>
heir skin but content character. today.<br>
C:\>

ちなみにコンパイラは次のように理解しているんだな。

open IN,"$ARGV[0]";
@A=<IN>;
close IN;
map{s/\n/<br>\n/g}@A;
print @A;

こんな風にしても同じ出力なんだな。

C:\>perl -ne "s/\n/<br>\n/g; print;" a.txt

コンパイラ的には次のような感じ。

open IN,"$ARGV[0]";
while(<IN>){
  s/\n/<br>\n/g;
  print;
}
close IN;

どっちがすっきりしているかってのは微妙なとこなんだな。でも配列の各要素について処理して結果を配列で返すような場合はmap{}を使ったほうがなんとなくお得な感じがするんだな。

話は変わるけど下に様にすると@Aの各要素中に含まれるピリオドの数を出力できるんだな。

C:\>perl -e "@A=<>; @A=map{s/\./<br>\n/g}@A; print map{\"in line \".++$i.\" a \".$_.\" times\n\"}@A;" a.txt
in line 1 a  times
in line 2 a  times
in line 3 a  times
in line 4 a  times
in line 5 a 1 times
in line 6 a  times
in line 7 a 1 times
in line 8 a  times
in line 9 a  times
in line 10 a 1 times
in line 11 a  times
in line 12 a 2 times
C:\>

行末の2個以上連続する空白、タブを削除

D:\>perl -pe "s/(  +|\t\t+)$//g" test.html
    4 spaces 1 space
                2 tabs  1 tab
D:\>perl -pe "" test.html
    4 spaces 1 space
                2 tabs  1 tab
D:\>

連続する空白を削除

D:\>perl -pe "s/(  +|\t\t+)//g" test.html
4 spaces 1 space
2 tabs  1 tab
D:\>perl -pe "" test.html
    4 spaces 1 space
                2 tabs  1 tab
D:\>

連続する改行を削除

Unix$ perl -0 -pe 's/\n+/\n/g;'
Win32> perl -0 -pe "s/\n+/\n/g;"

-0777とはファイルを一気に読み込んでPerlワンライナーに渡すオプション

D:\>perl -0777 -pe "print" test.html
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
</body>
</html>
<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
</body>
</html>
D:\>

例えばこんな感じ。こいつをPerlインタプリタは下のように解釈しているんだな。

while(<>){
  print;
} continue {
  print;
}

出力を見るとよくわかるけど、whileループ中のprint文によって出力された変数内にはtest.htmlの内容全てが収められているんだな。だから、出力中の1番目の<html>から</html>まではwhileループ中のprint文で一気に出力され、2番目の<html>から</html>まではcontinue中のprint文で一気に出力されるんだな。これに対して、下のように書いた場合は結果が変わるんだな。

D:\>perl -pe "print" test.html
<html><head><title>PerlTest</title></head>
<html><head><title>PerlTest</title></head>
<body>
<body>
<h1>PerlTestBody</h1>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
<hr><p>AllAboutPerlOneLiner</p>
</body>
</body>
</html>
</html>
D:\>

解釈は上の場合と全く同じなんだけど、whileループに渡されたダイアモンド演算子中の内容が異なっているんだな。

while(<>){
  print;
} continue {
  print;
}

-0777をオプションで指定しない場合、ファイルから1行ずつ読み込んでその内容がダイアモンド演算子の内容になるから、ファイル中の内容を1行ごとwhileループ中のprint文で表示して、この後にもう一回continue中のprint文で同じ内容を表示しているんだな。試しに下のようにしてみると何回whileループ中の分を実行したかがよくわかるんだな。

D:\>perl -0777 -pe "$i++;print $i " test.html
1<html><head><title>PerlTest</title></head>
<body>
<h1>PerlTestBody</h1>
<hr><p>AllAboutPerlOneLiner</p>
</body>
</html>
D:\>

この場合は-0777オプションがついているので、test.html中の内容が一気に渡され、whileループは1回。

D:\>perl -pe "$i++;print $i " test.html
1<html><head><title>PerlTest</title></head>
2<body>
3<h1>PerlTestBody</h1>
4<hr><p>AllAboutPerlOneLiner</p>
5</body>
6</html>
D:\>

この場合は-0777オプションがついていないので、test.html中の内容が行ごとに渡され、whileループは行数回だけ繰り返され、6回。

気分的な問題だけど、渡すファイルのファイルサイズが小さい場合は、-0777オプションをつけたほうが速いような気がしたんだな。つまり、

D:\>perl -0777 -pe "" test.html

としたほうが、

D:\>perl -pe "" test.html

とするよりも体感速度が速いということ。まぁタイプする量が少ないほうが好きなんで下のほうをよく使っちゃいますが。WindowsのMS-DOSプロンプトを使う場合は、入力する文字数に制限があるので、この手の逃げを使うことも結構有用かと。

バックアップを残して操作

perl -i.bak -pe s/abc/ABC/g *.txt

[ファイル編集] ファイル中の特定のレコードを加工して出力

D:\backup\LaTeX\Stress_Distribution>perl -ane "next if @F==();print \"set label'($F[3]$F[4]$F[5])' at $F[13],$F[15]\n\";" imp.dat>t
D:\backup\LaTeX\Stress_Distribution>perl -ane "next if @F==();print \"set label'($F[3]$F[4]$F[5])' at $F[7],$F[15]\n\";" imp.dat>t
D:\backup\LaTeX\Stress_Distribution>perl -ane "next if @F==();print \"set label'($F[3]$F[4]$F[5])' at $F[10],$F[15]\n\";" imp.dat>t
D:\backup\LaTeX\Stress_Distribution>

[ファイル編集] ファイル中の特定のレコードだけ出力

C:\WINDOWS\デスクトップ>perl -F"<>" -alne "print\"@F[1]\";" b.txt
Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)
Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90)
Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90; Lunascape 2.0.3)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; (R1 1.5); .NET CLR 1.1.4322) Sleipnir/2.00
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NETCLR 1.1.4322)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; iebar)
Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1
C:\WINDOWS\デスクトップ>
open IN,"b.txt";
@F=<IN>;
chomp @F;close IN;
for ( $i=0 ; $i<=$#F ; $i++ ) {
        @H = split(/<>/,$F[$i]);
        for ( $j=0 ; $j<=$#H ; $j++ ){
                $G[$i][$j]=$H[$j];
        }
}
for ( $i=0 ; $i<=$#G ; $i++ ) {
        for ( $j=0 ; $j<=$#{$G[$i]} ; $j++ ){
                print "$G[$i][$j]\n";
        }
}

[ファイル編集]配列のオフセット値が負(-x)の場合には、配列の末尾から数えてx番目

例えば、空白で区切られた4列のデータファイルがあったとする。このとき、各行を空白で区切った内容は配列@Fに収められる。したがって、1行目、2行目、3行目、4行目の内容はそれぞれ、$F[0]、$F[1]、$F[2]、$F[3]に収められる。このとき、配列のオフセット値に負の数を指定すると、1行目、2行目、3行目、4行目の内容はそれぞれ、$F[-4]、$F[-3]、$F[-2]、$F[-1]に収められる。つまり、配列のオフセット値に負(-$n)を指定した場合、$F[-$n]の内容は、配列の末尾から数えて$n番目の内容と同じ、つまり$F[$n-$#F-1]ということなんだな。

C:\WINDOWS\デスクトップ>perl -lane "$n=1; print \"$F[$n-$#F-1] $F[$n]\"" b.dat
2.11520385742188 2.11520385742188
3.05591735839844 3.05591735839844
4.1234130859375 4.1234130859375
5.36311340332031 5.36311340332031
6.1601806640625 6.1601806640625
7.09348449707031 7.09348449707031
8.60143432617187 8.60143432617187
9.57536010742187 9.57536010742187
10.8480926513672 10.8480926513672
11.7131164550781 11.7131164550781
12.6625122070312 12.6625122070312
14.0559478759766 14.0559478759766
14.6789276123047 14.6789276123047
16.3958953857422 16.3958953857422
16.2184478759766 16.2184478759766
17.0652526855469 17.0652526855469
19.6688018798828 19.6688018798828
19.8661254882813 19.8661254882813
20.2305358886719 20.2305358886719
22.4033935546875 22.4033935546875
C:\WINDOWS\デスクトップ>

[数値計算] 任意の関数f(x)の表現

C:\>perl -e "$x=4; $a=1; $f[$x]=exp(-$a*$x**2); print $f[$x]"
1.12535174719259e-007
C:\>
C:\>perl -le "$a=1; for(1..10){ $x=$_; $f[$x]=exp(-$a*$x**2); print $f[$x]}"
0.367879441171442
0.0183156388887342
0.00012340980408668
1.12535174719259e-007
1.3887943864964e-011
2.31952283024357e-016
5.24288566336346e-022
1.60381089054864e-028
6.63967719958073e-036
3.72007597602084e-044
C:\>
C:\>perl -le "$a=1; $f[$x]=$x**2; for(1..10){ $x=$_; $g[$x]=exp(-$a*$x**2); print \"$x\t$f[$x]\t$g[$x]\";}"
1               0.367879441171442
2               0.0183156388887342
3               0.00012340980408668
4               1.12535174719259e-007
5               1.3887943864964e-011
6               2.31952283024357e-016
7               5.24288566336346e-022
8               1.60381089054864e-028
9               6.63967719958073e-036
10              3.72007597602084e-044
C:\>

うまくできたと思ったけど、こうするとだめなんだな。つまり上の場合、$f[$x]には何も含まれてはいないから表示もされないわけなんだな。これは$f[$x]を定義した式の右辺に$xが含まれていて、$xがこの時点で定義されていないためなんだな。そこで下のようにしてみるんだな。

C:\>perl -le "$a=1; sub f(){return exp(-$a*$_**2);} for(1..10){ print &f($x)}"
0.367879441171442
0.0183156388887342
0.00012340980408668
1.12535174719259e-007
1.3887943864964e-011
2.31952283024357e-016
5.24288566336346e-022
1.60381089054864e-028
6.63967719958073e-036
3.72007597602084e-044
C:\>

こうするとprint文が実行されたときにサブルーチン&fが引数$x付きで呼ばれて、目的の関数を毎回定義しなくてもよくなるんだな。また、こうすることで余分なメモリを使わなくてすむこともあるんだな。つまり、どうせ1回表示するだけならサブルーチンとして定義しておいたほうがいいということ。

[省文字] 乱数の生成

例えば、ある関数f(x)がg(x)とh(x)の線形結合であらわされている場合、f(x)=a*g(x)+b*h(x)と書けるんだな。計算するたびにこのaとbを適当な数に変化させ、xとf(x)を出力する場合を考えるんだな。

C:\>perl -le "@S = map{rand} @S[0..9]; print \"@S\";"
0.6502685546875 0.025390625 0.410919189453125 0.35791015625 0.797760009765625 0.361724853515625 0.340911865234375 0.92474365234375 0.505035400390625 0.580718994140625
C:\>

上のようにすると大量に乱数を変数にセットできるんだな。こいつを使ってある関数g(x)、h(x)の線形結合を出力してみるんだな。

C:\>perl -le "sub f{@S=map{rand}@S[0..3]; return $S[3]*$_**3+$S[2]*$_**2+$S[1]*$_+$S[0];} for(1..10){$f=&f($_); print \"$_ $f\";}"
1 2.25576782226563
2 7.90341186523438
3 13.4155578613281
4 35.8011169433594
5 49.6056213378906
6 233.369232177734
7 120.380859375
8 267.388641357422
9 58.6871643066406
10 589.035797119141
C:\>

上の例が最大にスクリプトを長く書いた例で、これ以上は入力できないんだな。これを改良してより多くの独立な変数を使うことを考えるんだな。

C:\>perl -le "sub f{($a,$b,$c,$d)=map{rand}@S[0..9]; return $d*$_**3+$c*$_**2+$b*$_+$a;} for(1..10){$f=&f($_); print \"$_ $f\";}"
1 1.01864624023438
2 2.65805053710938
3 31.4973754882813
4 5.29510498046875
5 135.414398193359
6 117.433990478516
7 191.104675292969
8 240.653472900391
9 701.856262207031
10 420.729797363281
C:\>

最初の改良例はこんな感じなんだな。確かにf(x)の表記は単純になったけど、その分だけmap{}から受ける配列が複雑になってプラマイゼロの内容なんだな。どうにかしてもう一つ余分な変数上の例では$eもう一つ上の例では$S[4]をどうにかしたいんだな。

[map{}を使え]データ処理

C:\>perl -le "sub f{@S=map{1+rand(0.1)}@S[0..3];return $S[3]*$_**3+$S[2]*$_**2+$S[1]*$_+$S[0]}for(-9..9){print $_.\" \".&f($_);}">a
C:\>

この後gnuplotで各変数が1.05に近ければうれしいんだな。

gnuplot> plot 'a', 'a' smooth csplines with lines,'a' smooth bezier,'a' using 1:2:(1.0) smooth acsplines
gnuplot> f(x)=d*x**3+c*x**2+b*x+a
gnuplot> fit f(x) 'a' via a,b,c,d
Final set of parameters            Asymptotic Standard Error
=======================            ==========================
a               = -1.53336         +/- 1.965        (128.1%)
b               = 0.818395         +/- 0.6023       (73.59%)
c               = 1.30882          +/- 0.0489       (3.736%)
d               = 1.0591           +/- 0.01028      (0.9706%)
correlation matrix of the fit parameters:
               a      b      c      d
a               1.000
b              -0.000  1.000
c              -0.747  0.000  1.000
d               0.000 -0.918 -0.000  1.000
gnuplot> plot 'a', 'a' smooth csplines with lines,'a' smooth bezier,'a' using 1:2:(1.0) smooth acsplines,f(x)
gnuplot>

ものすごく違うところがあるんだな。非常に不遜ながら標準偏差を考えれば一応あっていそうな感じがするんだな。ところでプロットの結果はやはり、ベジェ曲線やスプライン補間のほうが上手く実験の結果を表していることに気が付くんだな。各関数の形は単純な形なのにこれはかなりがっかりな結果なんだな。

[ファイル編集] 表計算

Schwartzian Transform Methodについて書いてみようと思うんだな。まずは下のスクリプトを見てほしいんだな。

test_STM.pl

# Schwartzian Transform Method
# First Section --- Difinition of @LIST ---
# Difinition of @LIST
@LIST = ('1,21,4'  ,
         '5,33,43' ,
         '15,2,5' ,
         '12,15,21');
print "@LIST\n";
# Second Section --- Get Refference of each line in @LIST ---
# For each elements in @LIST
foreach (@LIST){
  # Split now-specificated element in @LIST by ",".
  # In @tmp, there are 3 elements. These are values.
  my @tmp = split(/,/,$_);
  # Get @tmp's refference by "[ ]".
  # In @tmp, there are 3 elements. These are refferences.
  my @tmp = [@tmp];
  # Add them to @tmp_LIST
  # In @tmp_LIST, 3 elements added each times. These are refferences.
  push @tmp_LIST,@tmp;
}
print "@tmp_LIST\n";
# Forrowing is the same working
# map{} gets forrowing array and returns the result of the statement in { }.
# @tmp_LIST = map{[split(/,/,$_)]} @LIST;
# print "@tmp_LIST\n";
# Third Section --- Sort @LIST by using derefference of @tmp_LIST ---
# $a and $b is refference of @LIST.
# And derefference them by operator ->[]. So $a->[2] and $b->[2] are values themselves.
# Sort them by values. Rewrite @tmp_LIST.
# Sort gets a forrowing array and returns a value of the array by criterion in {}.
# In @tmp_LIST, there are 4 elements. These are refferences.
#@tmp_LIST = sort{$a->[2] cmp $b->[2]} @tmp_LIST;
# If sorting them by number is needed, forrowing is sutable.
@tmp_LIST = sort{$a->[2] <=> $b->[2]} @tmp_LIST;
print "@tmp_LIST\n";
# Fourth Section --- Join them ---
# For each elements in @tmp_LIST
foreach(@tmp_LIST){
  # Get values themselves refferenced by $_.
  my @tmp = @$_;
  # Join each elements in @tmp by ",".
  my @tmp = join(',',@tmp);
  # Add them to @NEW_LIST.
  push @NEW_LIST,@tmp;
}
print "@NEW_LIST\n";
# Forrowing is the same working
#@NEW_LIST = map{join',',@$_} @tmp_LIST;
#print "@NEW_LIST\n";
# All sections can be written in one line.
# Forrowing is One line style about upper sections.
#@NEW_LIST = map{join',',@$_} sort{$a->[2] cmp $b->[2]} map{[split',']} @LIST;
#@NEW_LIST = map{join',',@$_} sort{$a->[2] <=> $b->[2]} map{[split',']} @LIST;
#print "@NEW_LIST\n";

例えば下のようにするとa.dat中の1カラムにxの値、2カラムに二次関数の値がセットされるんだな。

C:\>perl -le "sub f{($a,$b,$c)=(rand,rand,rand);return $a*$_**2+$b*$_+$c;} for(-9..9){$x=$_+rand;$f=&f($x);print \"$x $f\";}">a.dat
C:\>

結果を表示すると下のようになるんだな。

C:\>perl -le "@a=<>; print @a;" a.dat
-8.77401733398438 72.4348754882813
-7.83602905273438 5.97018432617188
-6.059326171875 35.9939270019531
-5.44882202148438 33.2612609863281
-4.21817016601563 9.92474365234375
-3.3221435546875 -1.31829833984375
-2.09671020507813 3.02935791015625
-1.600341796875 2.762939453125
-0.749908447265625 0.922332763671875
0.154327392578125 0.6827392578125
1.42169189453125 2.23629760742188
2.8875732421875 2.50311279296875
3.66021728515625 7.56057739257813
4.10379028320313 19.9759826660156
5.96383666992188 5.775146484375
6.0418701171875 17.5700378417969
7.32489013671875 35.4329223632813
8.97604370117188 31.3730163574219
9.09332275390625 29.8526916503906
C:\>

これを上の方法を使って2カラム目でソートしてみるんだな。

C:\>perl -le "@a=<>; @b=map{join' ',@$_}sort{$a->[1]<=>$b->[1]}map{[split/\s/]}@a; for(@b){print}" a.dat
-3.3221435546875 -1.31829833984375
0.154327392578125 0.6827392578125
-0.749908447265625 0.922332763671875
1.42169189453125 2.23629760742188
2.8875732421875 2.50311279296875
-1.600341796875 2.762939453125
-2.09671020507813 3.02935791015625
5.96383666992188 5.775146484375
-7.83602905273438 5.97018432617188
3.66021728515625 7.56057739257813
-4.21817016601563 9.92474365234375
6.0418701171875 17.5700378417969
4.10379028320313 19.9759826660156
9.09332275390625 29.8526916503906
8.97604370117188 31.3730163574219
-5.44882202148438 33.2612609863281
7.32489013671875 35.4329223632813
-6.059326171875 35.9939270019531
-8.77401733398438 72.4348754882813
C:\>

たったこれだけでソートできてしまうんだな。キモはmap{}を使うとこなんだな。これを使うとforeach{}文で配列にpushするような場合のスクリプトを短く書けるんだな。

[Perl] 多数のカラムでソート(複数キーソート)

#c.f. perl ソート 複数
@yy = map{join'<>',@$_}sort{($a->[0]cmp$b->[0]) or ($b->[2]<=>$a->[2]) or ($a->[1]cmp$b->[1])}map{[split/<>/]}@yy;

最初に1カラム目でソート、次に3カラム目でソート、最後に2カラム目でソートしているんだな。つまり、ソートの優先順位は1、3、2なんだな。例えばこんな感じに結果を得るんだな。

HTTP_ACCEPT<>*/*<>147
HTTP_ACCEPT<>text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5<>1
HTTP_ACCEPT_CHARSET<>Shift_JIS,utf-8;q=0.7,*;q=0.7<>1
HTTP_ACCEPT_ENCODING<>gzip, deflate<>140
HTTP_ACCEPT_ENCODING<>gzip,deflate<>1
HTTP_ACCEPT_LANGUAGE<>ja<>146
HTTP_ACCEPT_LANGUAGE<>ja,en-us;q=0.7,en;q=0.3<>1
HTTP_ACCEPT_LANGUAGE<>ja-jp<>1
HTTP_ACCEPT_LANGUAGE<>zh-tw<>1
HTTP_CACHE_CONTROL<>max-age=259200<>1
HTTP_CONNECTION<>Keep-Alive<>145
HTTP_CONNECTION<>keep-alive<>3

[Perl] ログファイルを見やすい形に整形

たとえば次のようなデータファイルがあったとするんだな。

HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)<>5
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90)<>39
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90; Lunascape 2.0.3)<>25
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)<>4
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; InfoPath.1)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)<>9
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; (R1 1.3); .NET CLR 1.1.4322)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; (R1 1.5); .NET CLR 1.1.4322) Sleipnir/2.00<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.50727)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)<>14
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.2) Sleipnir/2.10<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; iebar)<>1
HTTP_USER_AGENT<>Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; istb 702)<>1
HTTP_USER_AGENT<>Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ja-jp) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1<>1
HTTP_USER_AGENT<>Mozilla/5.0 (Windows; U; Win 9x 4.90; ja-JP; rv:1.7.12) Gecko/20050919 Firefox/1.0.7<>1

<>で区切られたこのファイルを3カラム目でソートすることを考えるんだな。これは今までの話から次のようにすればいいことがわかるんだな。

C:\WINDOWS\デスクトップ>perl -le "@a=<>; @b=map{join' ',@$_}sort{$b->[2]<=>$a->[2]}map{[split/<>/]}@a; for(@b){print}" b.txt

[Perl] 表計算フィルタ

よくあるデータの形式として、1列目にxの値、2列目にyの値を出力されたデータファイルがあるんだな。これらのデータの組を読み込んで加工して、新しいファイルを作ることを考えるんだな。

とりあえずテストファイルとして、下の様にして標準出力1列目にxの値、2列目にexp(x)の値を出力してみるんだな。

D:\>perl -le "sub f(){return exp($_);} for(1..20){$f=&f($_); print \"$_ $f\";}"
1 2.71828182845905
2 7.38905609893065
3 20.0855369231877
4 54.5981500331442
5 148.413159102577
6 403.428793492735
7 1096.63315842846
8 2980.95798704173
9 8103.08392757538
10 22026.4657948067
11 59874.1417151978
12 162754.791419004
13 442413.39200892
14 1202604.28416478
15 3269017.37247211
16 8886110.52050787
17 24154952.7535753
18 65659969.1373305
19 178482300.963187
20 485165195.40979
D:\>

実行結果が確認できたからこれをパイプ処理でa.datに書き込むんだな。ここまででデータファイルができたんだな。

D:\>perl -le "sub f(){return exp($_);} for(1..20){$f=&f($_); print \"$_ $f\";}">a.dat
D:\>

次はこれを加工することを考えるんだな。-aオプションを使ってオートスプリットモードとし、各列の内容を特殊配列@Fに読み込むんで、これを加工するんだな。ここでは、入力ファイルa.datの1列目の逆数を取り、2列目の自然対数を取ったんだな。

D:\>perl -alne "$F[0]=1/$F[0]; $F[1]=log($F[1]); print \"$F[0] $F[1] \"" a.dat
1 1
0.5 2
0.333333333333333 3
0.25 4
0.2 5
0.166666666666667 6
0.142857142857143 7
0.125 8
0.111111111111111 9
0.1 10
0.0909090909090909 11
0.0833333333333333 12
0.0769230769230769 13
0.0714285714285714 14
0.0666666666666667 15
0.0625 16
0.0588235294117647 17
0.0555555555555556 18
0.0526315789473684 19
0.05 20
D:\>

少し見づらいけど、1行目には1/$x、2行目にはlog(exp($x))=$xが表示されていることがわかるんだな。

[数値計算] 最小二乗法フィルタ

あるデータファイルに記載されたxとyの関係(実験の測定値)が次の関数でを満たしていると仮定するんだな。y=a*x+b。これから最小二乗法でaとbを定める事を考えるんだな。例えばデータファイルは「xの値、スペース、yの値、改行」のような書式となっているとするんだな。

とりあえずデータファイルを次のようにして生成したんだな。

C:\>perl -le "sub f(){$a=1+rand(0.1);$b=1+rand(0.1);return $a*$_+$b;} for(1..20){$x=$_+rand(0.1);$f=&f($x);print \"$x $f\";}">a.dat
C:\>

これにより生成したa.datの内容を表示すると下のようになるんだな。

C:\>perl -pe "" a.dat
1.01757202148438 2.11520385742188
2.05708923339844 3.05591735839844
3.03453979492188 4.1234130859375
4.03664245605469 5.36311340332031
5.02697448730469 6.1601806640625
6.01442565917969 7.09348449707031
7.04530944824219 8.60143432617187
8.09518127441406 9.57536010742187
9.06552429199219 10.8480926513672
10.0904388427734 11.7131164550781
11.0158996582031 12.6625122070312
12.0860198974609 14.0559478759766
13.0393676757813 14.6789276123047
14.0699066162109 16.3958953857422
15.0349884033203 16.2184478759766
16.0701873779297 17.0652526855469
17.0621643066406 19.6688018798828
18.0716796875 19.8661254882813
19.0795959472656 20.2305358886719
20.0910125732422 22.4033935546875
C:\>

最小自乗法で必要な∑x_n*x_n、∑y_n*x_n、∑x_n、∑y_n、∑1を求めるために、それぞれのxについてx_n*x_n、y_n*x_nを求めるんだな。

C:\>perl -alne "push @F,$F[0]**2,$F[0]*$F[1]; print \"@F\";" a.dat
1.01757202148438 2.11520385742188 1.03545281890781 2.15237226504834
2.05708923339844 3.05591735839844 4.23161611416378 6.28629469611683
3.03453979492188 4.1234130859375 9.20843176696452 12.512661100179
4.03664245605469 5.36311340332031 16.2944823180232 21.6489712604787
5.02697448730469 6.1601806640625 25.2704724960123 30.9670710354299
6.01442565917969 7.09348449707031 36.173316009799 42.663235172173
7.04530944824219 8.60143432617187 49.6363852214907 60.5997665266134
8.09518127441406 9.57536010742187 65.531959865624 77.5142758373729
9.06552429199219 10.8480926513672 82.1837306887005 98.3436474527513
10.0904388427734 11.7131164550781 101.81695603975 118.190485248248
11.0158996582031 12.6625122070312 121.350045279599 139.488963893428
12.0860198974609 14.0559478759766 146.071876961821 169.880465706726
13.0393676757813 14.6789276123047 170.02510938421 191.403934223019
14.0699066162109 16.3958953857422 197.962272188895 230.688716966556
15.0349884033203 16.2184478759766 226.050876287976 243.844175735163
16.0701873779297 17.0652526855469 258.250922361771 274.241808308457
17.0621643066406 19.6688018798828 291.117450826801 335.592329389322
18.0716796875 19.8661254882813 326.5856067276 359.014256455899
19.0795959472656 20.2305358886719 364.030981510914 385.990450552516
20.0910125732422 22.4033935546875 403.648786218176 450.10686159052
C:\>

確認できたらこいつをパイプ処理でb.datに出力してするんだな。

C:\>perl -alne "push @F,$F[0]**2,$F[0]*$F[1]; print \"@F\";" a.dat>b.dat

次に各行の和とデータの数を出力するんだな。

C:\>perl -alne "$x+=$F[0];$y+=$F[1];$xx+=$F[2];$xy+=$F[3];$n=$.;END{print \"$x $y $xx $xy $n\";}" b.dat>c.dat
C:\>perl -pe "" c.dat
211.10451965332 241.895156860352 2896.4767310872 3251.13074341602 20 
C:\>

最後に、下のように行列計算を行って結果を出力するんだな。

[∑y_n*x_n]=[∑x_n*x_n ∑x_n][a]
[∑y_n    ] [∑x_n     ∑1  ][b]
C:\WINDOWS\デスクトップ>perl -alne "$d=$F[2]*$F[4]-$F[0]**2;@a=(($F[4]*$F[3]-$F[0]*$F[1])/$d,($F[2]*$F[1]-$F[0]*$F[3])/$d);END{print \"@a\";}" c.dat
1.04437437081074 1.07115034860561
C:\WINDOWS\デスクトップ>

1つ目がa、2つ目がbなんだな。自信がないのでgnuplotで確かめてみるんだな。

gnuplot> fit a*x+b 'a.dat' via a,b
Final set of parameters            Asymptotic Standard Error
=======================            ==========================
a               = 1.04437          +/- 0.01597      (1.529%)
b               = 1.07115          +/- 0.1922       (17.94%)
correlation matrix of the fit parameters:
               a      b
a               1.000
b              -0.877  1.000
gnuplot> print a
1.04437437082676
gnuplot> print b
1.07115034838608
gnuplot> plot 'a.dat',a*x+b

確かに近い値となっていることがわかるんだな。完成したのでGIFで出力しておくんだな。

gnuplot> set terminal gif
Terminal type set to 'gif'
Options are 'small size 640,480 '
gnuplot> set output 'a.gif'
gnuplot> plot 'a.dat',a*x+b
gnuplot>

[数値計算] 数値積分(ガウス積分)

use Math::Trig;
$a     = 1;
$x_max = 5;
$x_min = 0;
$I_r=(1*2**-1)*sqrt(pi()*$a**-1);
$f[$x] = exp(-$a*$x**2);
$x_div = 20000000;  #精度向上因子
$i_max = ($x_max-$x_min)*$x_div;
for($i = 0 ; $i < $i_max ; $i++ ){
  $dx    = 1/$x_div;
  $x     = $i*$dx;
  $y     = exp(-$a*$x**2);
  $I    += $y*$dx;
  $dI    = $I_r-$I;
  last if($dI<=0);
  print "$x\t$y\t$I\t$dI\n" if ($i%($i_max/10)==0);
 # sleep(1);
}
print "$x\t$y\t$I\t$dI\n";
print "$I_r\n" ;
exit;
C:\>perl gaus.pl
0       1       5e-008  0.886226875452758
0.5     0.778800783071405       0.461281050882873       0.424945874569885
1       0.367879441171442       0.74682416700966        0.139402758443098
1.5     0.105399224561864       0.856188421260223       0.0300385041925354
2       0.0183156388887342      0.882081416220737       0.00414550923202095
2.5     0.00193045413622771     0.885866298666317       0.000360626786440998
3       0.00012340980408668     0.886207373263146       1.95521896119155e-005
3.5     4.78511739212901e-006   0.88622629189962        6.33553137507903e-007
3.92599695      2.02312551357165e-007   0.886226925452761       -3.10862446895044e-015
0.886226925452758
C:\>
C:\WINDOWS\デスクトップ>perl gauss.pl
5e-008  0.886226875452758
0.0500000500009228      0.836226875451835
0.100000050002361       0.786226875450397
0.150000050003798       0.73622687544896
0.200000050005236       0.686226875447522
0.250000050006674       0.636226875446084
0.300000049980356       0.586226875472402
0.350000049954038       0.53622687549872
0.400000049927721       0.486226875525037
0.450000049901403       0.436226875551355
0.500000049875085       0.386226875577673
0.550000049904278       0.33622687554848
0.600000049933472       0.286226875519286
0.650000049962665       0.236226875490093
0.700000049991858       0.1862268754609
0.750000050021052       0.136226875431706
0.800000050050245       0.0862268754025128
0.850000050079438       0.0362268753733195
0.8862269       1       0.88622695010059        -2.46478322196708e-008
0.886226925452758
C:\WINDOWS\デスクトップ>
C:\WINDOWS\デスクトップ>perl -le "$a=1; $x=1; for(0..10){ $x=$_; $y += exp(-$a*$x**2); print$y};"

[数値計算] 有理数の積と浮動小数点演算

C:\>perl -e "@d=(1,2,3,4); $c=$d[0]/$d[1]+$d[2]/$d[3]; $a=($d[0]*$d[3]+$d[2]*$d[1])/($d[1]*$d[3]); print \"$c $a\";"
1.25 1.25
C:\>
C:\>perl -e "@d=(1,2,3,4); @r=&d(@d); print $r[0]/$r[1]; sub d(){ $s=$_[0]*$_[3]+$_[2]*$_[1]; $i=$_[1]*$_[3]; return ($s,$i);}"
1.25
C:\>
C:\>perl -le "for(1..10){@r=&d(($_,$_+1,$_+1,$_+1)); print \"$r[0] $r[1]\";} sub d(){return $_[0]*$_[3]+$_[2]*$_[1],$_[1]*$_[3];}"
6 4
15 9
28 16
45 25
66 36
91 49
120 64
153 81
190 100
231 121
C:\>
C:\>perl -le "for(1..20){@d=($_++,$_++,$_++,$_++); @r=&d(@d); print \"@d @r\";} sub d{return $_[0]*$_[3]+$_[2]*$_[1],$_[1]*$_[3];}"
1 2 3 4 10 8
2 3 4 5 22 15
3 4 5 6 38 24
4 5 6 7 58 35
5 6 7 8 82 48
6 7 8 9 110 63
7 8 9 10 142 80
8 9 10 11 178 99
9 10 11 12 218 120
10 11 12 13 262 143
11 12 13 14 310 168
12 13 14 15 362 195
13 14 15 16 418 224
14 15 16 17 478 255
15 16 17 18 542 288
16 17 18 19 610 323
17 18 19 20 682 360
18 19 20 21 758 399
19 20 21 22 838 440
20 21 22 23 922 483
C:\>

下のようにやっても同じなんだな。

C:\>perl -le "for(1..20){@d=($_..$_+3);@r=&d(@d);print \"@d @r\";} sub d{return $_[0]*$_[3]+$_[2]*$_[1],$_[1]*$_[3];}"

等差数列の計算

C:\>perl -e "$a=1; $d=1; for (1..100) { $S+=$a+($_-1)*$d; } print $S"
5050
C:\>

これは少し便利かも知れないぞ。でも少し便利なだけ。等差数列には和の公式があるから。それを使ったほうが断然早い。特に1から10000000000間での計算とかやらせたとき。それにこの数が大きすぎると「数が大きすぎるぞコラ」メッセージが出る。例えばこんな感じに。

C:\>perl -e "$a=1; $d=1; for (1..100000000000000000) { $S+=$a+($_-1)*$d; } print $S"
Range iterator outside integer range at -e line 1.
C:\>

それよりもlオプションを使って、各項の値anとそのときの和Snを表示したほうがうれしいかもしれないな。例えばこんな風に。

C:\>perl -le "$a=1; $d=1; $an=0; for (1..10) { $an=$a+($_-1)*$d; $S+=$an; print"$an"."_"."$S";} print $S"
1_1
2_3
3_6
4_10
5_15
6_21
7_28
8_36
9_45
10_55
55
C:\>

それにしてもprint文の中でスペースはどう考えればいいのだろう。それはこうすればいいのだ。

C:\>perl -le "$a=1; $d=1; $an=0; for (1..10) { $an=$a+($_-1)*$d; $S+=$an; print "$an".' '."$S";} print $S"
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55
55
C:\>

等比数列の計算

等差の次は等比ということで。代わり映えはしませんが。

D:\>perl -e "$a=1;$r=1;for (1..100) {$S+=$a*$r**($_-1);} print $S"
100

-10から10まで足し算の式とその結果

ただのお遊びですな。範囲演算子でマイナスが使えるんですねぇ。ちなみに文字を足し算しても0にしかなりません

D:\>perl -e "for (-10..10) {print; $_!=100 && print '+'; $n+=$_;} print '='.$n"
-10+-9+-8+-7+-6+-5+-4+-3+-2+-1+0+1+2+3+4+5+6+7+8+9+10+=0

アルファベットを範囲演算子で表示する2

D:\>perl -e "for (a..z,A..Z,0..9) { print $_ }"
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

こんな使い方もあるんだと思った作品へぇ。

アルファベットを範囲演算子で表示する1

どっちが美しいかは好みですが。wオプションをつけないで実行すると致命的なエラー以外はエラー表示を抑止してくれるんだな。例えば下の1番目と2番目はwオプション無しと有りの場合を示するんだな。エラーを修正したものが3番目なんだな。3番目ははっきり言って冗長なんだな。まぁ1番目の方法でもうまいこと出力できているから良いといえばそれまでなんだけど。

C:\>perl -e "print (a..z,A..Z,0..9)"
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
C:\>perl -we "print (a..z,A..Z,0..9)"
print (...) interpreted as function at -e line 1.
Unquoted string "a" may clash with future reserved word at -e line 1.
Unquoted string "z" may clash with future reserved word at -e line 1.
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
C:\>perl -we "print join'',('a'..'z',A..Z,0..9)"
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
C:\>

文字列を改行せずに表示する

C:\>perl -e "print "'ww'";"
ww
C:\>

例えば、echoコマンドではこのようなことはできないんだな。下のようなコマンドを打つとその下のように必ず改行されて表示されるんだな。通常のファイル追記の場合はさほど問題はない(自動的に改行されたほうがよい)けど、困るときも時々あるんだな。

C:\>echo ww
ww
C:\>

unixの場合は\nを含めることで改行の可否が選択可能だというような話も聞いたことがあるけど、Windowsの場合はあまり親切なechoコマンドではないようなんだな。

文字列を複数行表示する

D:\>perl -e "print \"xxx\nyyy\nzzz\n\";"
xxx
yyy
zzz
D:\>

lオプションを使うのもいいけど、こっちのほうが汎用的でいいかも。複数行を一気に表示したいときに使えるんだな。ここまでくるとUNIXのechoコマンドにずいぶんと近づいてきたという感じなんだな。

注意が必要な文字

D:\>perl -e "print \"!"#$%&'()=~|-^\`{@[+*};:]<>?_,./\";"
!#0&'()=~|-^`{@[+*};:]<>?_,./
D:\>

変数展開ルール

D:\>perl -e "$n=ss; print \"ww$n\n\";"
wwss
D:\>

上でも書いたけど、エスケープすることで変数展開できるようになるんだな。上のコマンドはPerlに下のように解釈されているんだな。書き下してみると変数展開やメタ文字の解釈が行われるのもぜんぜん不思議じゃないんだな。

$n=ss; print "ww$n\n";

例えば、エスケープしないと変数展開されないどころか、コンパイルもうまくできないんだな。

D:\>perl -e "$n=ss; print "ww$n\n";"
Backslash found where operator expected at -e line 1, near "$n\"
        (Missing operator before \?)
syntax error at -e line 1, near "$n\"
Execution of -e aborted due to compilation errors.
D:\>

これに対してコーテーションにすると変数展開もされないし、メタ文字の解釈もされないんだな。

D:\>perl -e "$n=ss; print 'ww$n\n';"
ww$n\n
D:\>

範囲演算子で文字らしいASCII文字を表示する

D:\>perl -e "for (0x20..0x7E) { print chr($_); }"
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
D:\>

上の結果はあまり美しくないんで制御文字を取っ払ってみたんだな。これはいざとなれば文字コードとchr関数で文字を表現できることを示唆しているんだな。たとえば、上と同じような出力を得ようとして下のようにするとエラーが出ていることがわかるんだな。

D:\>perl -e "print \" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\";"
syntax error at -e line 1, near "\]"
Execution of -e aborted due to compilation errors.
D:\>

これは以下のようにすると解決できるんだな。

D:\>perl -e "print \" !\".'\"'.\"#\$%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\";"
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
D:\>

解決するといってもこのコマンドは長すぎて、WindowsMeのMS-DOSプロンプトでは入力できないんだな。入力バッファの最大サイズは255バイトよりも増やせない事がその理由なんだな。Windows2000やWindowsXPなんかのコマンドプロンプトは入力バッファサイズに足かせがないから入力できるんだな。といってもここで注目すべきなのは、エスケープの仕方なんだから結果が見えていればいいんだな。さて、円マークでエスケープしたダブルクヲートに挟まれた文字のうちで唯一挙動の違うのは、ダブルクヲートなんだな。こいつだけはシングルクヲートで囲んで文字列連結で出力するしかなさそうなんだな。これ以外の文字は通常のエスケープがそのまま適応できるようなんだな。

[文字出力]Wide character in print at -e line 1.に困ったら

C:\>perl -e "binmode(STDOUT,':utf8'); print map{chr($_)}(0x00..0x1ff);"
C:\>

文字列を改行せずにファイルに書き込む

D:\>perl -e "print "'ww\n'";">test.txt

Windowsのechoコマンドの引数をパイプ処理でファイルに書き込む(D:\>echo "aaa" > test.txt)と必ず改行が後ろにつくけど、こうすると改行なしでファイルに書き込める。問題は複数の行を一度に書き込むにはどうするかだな。

改行コードをつけて文字列をファイルに書き込む。

D:\>perl -l -e "print "'ww'";">test.txt

ActivePerlなのかWindowsの仕様かとにかくprint文の中に\nを書き込んでも改行されないようだ。と言うことは改行するためだけに何行もprint文を書かねばならんのか。

アルファベットや数字以外をどう書くか

D:\>perl -e "print "'!\"#$%&\'()=-~^|\`@{[+;*:}]<,>.?/_\\'";"
!"#$%&'()=-~^|\`@{[+;*:}]<,>.?/_\

問題はどうやって1文字分のスペースを表現するかだな。

ファイルの内容を表示する3

D:\>perl -pe "" test.html
<html><head><title>PerlTest</title></head>
<body>PerlTestBody</body>
</html>
D:\>

ファイルの内容を表示するだけならこれで十分かも。-pオプションをつけてperlを起動すると、-eオプションで指定した1行スクリプトをwhikeループ中に実行し、次のループの前処理を行なうcontinue文の中でprint文を実行するんだな。つまり下のように解釈されたんだな。

while (<>) {
} continue {
  print;
}

ファイルの内容を加工して表示する(Google 検索結果から不要な情報を切る)

例えばsite:blog.mag2.com perlとかsite:www.mag2.com perlとかで検索した結果をtxtで保存してこれからマッチする行を取り出す。

C:\WINDOWS\デスクトップ> perl -lne "if(s/^((www|blog).mag2.com\/(m\/|m\/log\/)[0-9]*(.html|[0-9]*))( - [0-9])//){print'http://'.$1;}" mag.txt>mag2.txt
C:\WINDOWS\デスクトップ>

ファイルの内容を表示する2

C:\>perl -p -e 'print' test.txt

-pで入力ファイルを指定、-eの後に続くはスクリプト文。print文を囲むのはシングルクヲート。ダブルにすると多分うまくいかない。

ファイルの内容を表示する1

C:\>perl -p -e 'print;' test.txt

print文の最後にセミコロンをつけてもうまくいく。

ファイルの内容を1行につき2回表示する

C:\>perl -p -e "print;" test.txt

ダブルコーテーションにすると全ての行を2回づつ表示するのなんでだろ。

ファイルの内容に行番号を付加してを表示する

C:\>perl -p -e "print "$o++"."':'";" test.txt

メールサーバにログイン(受信せず)

use Net::POP3;
my $server = 'pop.mail.yahoo.co.jp';
my $pop3  = Net::POP3->new($server) or die "Can't not open account.";
my $account  = 'ACCOUNT';
my $password = 'PASSWD';
my $count = $pop3->login($account, $password);
   $pop3->quit;
D:\>perl -MNet::POP3 -e "$pop3 = Net::POP3->new('pop.mail.yahoo.co.jp');$pop3->login('ACCOUNT','PASSWD');$pop3->quit;"
D:\>

メール送信

use Net::SMTP;
$smtp = Net::SMTP->new('smtp.mail.yahoo.co.jp');
$smtp->mail('SENDERADDRESS');
$smtp->to('GETTERADDRESS');
$smtp->data("To: My Dearest User\r\n\r\nA simple test message.\r\nIs anything wrong?\r\n")
$smtp->quit;
exit;
c:\>perl -MNet::SMTP -e "$s=Net::SMTP->new('smtp.mail.yahoo.co.jp');$s->mail('SEND');$s->to('GET');$s->data(\"\nMessage\n\");$s->quit;"

このままではWinMEのMS-DOSプロンプトでは文字数制限に引っかかるんだな。一応の解決策としては、バッチファイル中にこのプログラムを書き込んで使うという方法なんだな。でもそんなことするくらいなら、plファイルに書き込んでおいて実行するほうがずっと賢いんだな。

グロブとは何ぞや

C:\WINDOWS\デスクトップ>perl -e "$L=Hello; @L=(1,2,3,4); *s=*L; print \"$s\n\"; print \"$s[1]\";"
Hello
2
C:\WINDOWS\デスクトップ>

一体グロブとは何ぞや。

*s = *L;

こうすることでLという名前の付いた変数($L)、ハッシュ(@L)、配列(%L)、サブルーチン(&L)をそれぞれ、sという名前の付いた変数($s)、ハッシュ(@s)、配列(%s)、サブルーチン(&s)をとして参照できるんだな。つまり、ものすごく長い名前の変数($verylooooooooooooooooooooongname)を作っても、グロブでこの変数名と短い名前を対応付けておくことで、長い変数名を何回も使わなくてもよくなるということ。

C:\>perl -e "$verylooooooooooooooooooooongname = \"longlong\"; *s = *verylooooooooooooooooooooongname; print $s;"
longlong
C:\>

書き下せば

$verylooooooooooooooooooooongname = "longlong";
*s = *verylooooooooooooooooooooongname;
print $s;

ということ。

スカラ変数に対するリファレンスのいろは

D:\>perl -e "$word = \"A\"; $ref_word = \$word; print $ref_word;"
SCALAR(0x1555d70)
D:\>

こういうことがわざわざファイルを作らなくてもいいということが一行スクリプトのうれしい点かも。まぁそれはおいといて。上は変数$wordに文字列Aを代入して、変数$wordのリファレンス(メモリアドレス)を取得して、これを表示しているんだな。つまりこんな感じなんだな。

$word     = "A";
$ref_word = \$word;
print $ref_word;

変数名の前につけた\のことをリファレンス演算子と呼ぶんだな。この演算子の付いたスカラ変数の中身は変数と対応したメモリアドレスになっているんだな。

D:\>perl -e "$word = \"A\"; $ref_word = \$word; print $$ref_word;"
A
D:\>

こんな風にしてみるとまた違った出力が得られるんだな。こいつは書き下すと下のような感じになるんだな。

$word     = "A";
$ref_word = \$word;
print $$ref_word;

リファレンスの含まれる変数の前にドルマーク$を加えて出力するとリファレンスの指し示す変数の内容が出力されるんだな。

perl - [リファレンス] リファレンステスト(1)

リファレンスとは何ぞやということがわかっていなかったので、テストコードを書いてみた。なんとなくわかった気がする。それでもわからなきゃData::Dumperでも使ってなってことだ。

@LoL = ( ["00", "01"], ["10", "11", "12"], ["20"] );
  print $LoL[1][1];
#  11
#    $ref =\@LoL;
#    print $ref;
@value = @LoL;
print "\n";
foreach my $ref_array (@value){ 
print $ref_array;
 foreach (@$ref_array){ 
    print $_.","; 
 } 
 print "\n"; 
}
print @LoL;
#  ARRAY(0x83c38)ARRAY(0x8b194)ARRAY(0x8b1d0)
exit;

perl - [リファレンス] リファレンステスト(2)

リファレンスとは何ぞやということがわかっていなかったので、テストコードを書いてみた。

# generate unnamed array by [ ]@LoL = ( ["1" , "21", "21" ],
                ["5" , "33", "43" ],
                ["12", "15", "21" ] );
# in array @LoL, thera are references of each element quarted by [ ]print "@LoL\n";# that is why, each refferance $_ operated ->[0] means the real value of 1,5,12# and, operated ->[1] and ->[2] are understanded in the same way.foreach (@LoL){
        print $_."\t".$_->[0]."\t".$_->[1]."\t".$_->[2]."\n";
}# to sort values on 3rd column, How to?# first, get values and refferences on 3rd column# and generte hash %column like key:refference value:value.foreach (@LoL){
        $column{$_} = $_->[2];
}print %column;print "\n";# second, sort values in hash and get the rasult in array @column_sorted.# third, foreach my $value (sort {$a<=>$b} values %column){
        foreach my $key ( keys %column ){
                if ( $value eq $column{$key} ){
                        $value2 = delete $column{$key};
                        print "$value2\t$key\n";
                        push @LoL2,$key;
                        last;
                }
        }
}print "@LoL2\n";# on the end here is a sorted LoL by the numberes on 3rd columnforeach my $ref (@LoL2){
        foreach (@LoL){
                if ($_ eq $ref){
                        print $_."\t".$_->[0]."\t".$_->[1]."\t".$_->[2]."\n";
                        last;
                }
        }
}
exit;

リファレンスでどうしようもなくなったらDumperを使え(test_reference.pl)

#!/usr/bin/perl -w
#test_reference.pl
use strict;
my @LoL = ( ["00", "01"], ["10", "11", "12"], ["20"] );
use Data::Dumper; #for using Dumper()
print Dumper(@LoL);
exit;
D:\test>perl -w test_reference.pl
$VAR1 = [
          '00',
          '01'
        ];
$VAR2 = [
          '10',
          '11',
          '12'
        ];
$VAR3 = [
          '20'
        ];

お手軽にhttpリクエストをダンプします。

C:\>perl -MHTTP::Daemon -e "warn HTTP::Daemon->new(LocalPort =>80)->accept->get_request->as_string"
GET / HTTP/1.1
Connection: Keep-Alive
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ja
Host: 219.209.188.79
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90; Lunascape 2.0.3)

HTTP::Daemonで簡単なhttpサーバ

use HTTP::Daemon;
use HTTP::Status;
my $d = new HTTP::Daemon;
$d = new HTTP::Daemon
  LocalAddr => '',
  LocalPort => 80;
print "Please contact me at: <URL:", $d->url, ">\n";
while (my $c = $d->accept) {
  while (my $r = $c->get_request) {
  warn $r->as_string;
    if ($r->method eq 'GET' and $r->url->path eq "/") {
        $c->send_file_response("./Dd21.txt");
    } else {
        $c->send_error(RC_FORBIDDEN)
    }
  }
  $c->close;
  undef($c);
}
exit;
D:\test>perl -w http_deamon_deamon.pl
Please contact me at: <URL:http://eizi/>
GET / HTTP/1.1
Connection: Keep-Alive
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ja
Host: 219.209.188.83
If-Modified-Since: Tue, 27 Sep 2005 08:00:00 GMT
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90; Lunascape 2.0.3)

httpリクエストをダンプする

InetSpy (+ 横取り丸)Proxomitronのいずれかでやってましたが、今日LindowsOS開発日誌見ていたらおもしろいの見つけました。

お手軽にHTTPプロキシサーバをたてます。(not work on ActivePerl)

$ perl -MHTTP::Proxy -e 'HTTP::Proxy->new(port => 8080)->start'

HTTP::Proxyでエラー以外のログを有効する方法

エラー以外のログを有効にしたい場合は以下のような感じです。ただし、ActivePerlでは使えないようです。

$ perl -MHTTP::Proxy=:log -e '$p=HTTP::Proxy->new(port => 8080);$p->logmask(ALL);$p->start;'

FS2HTML

D:\>perl -i.bak -p -e "s/^(.*)\n/<p>$1<\/p>\n/g" a.html
D:\>perl -i.bak -p -e "s/^<p>!!!(.*)<\/p>\n/<h1>$1<\/h1>\n/g" a.html
D:\>perl -i.bak -p -e "s/^<p>\s(.*)<\/p>\n/<div>$1<\/div>\n/g" a.html
D:\>

telnetは付加、Perlは可。このとき1行スクリプトをどう動かすか

telnetの使えないサーバで、Perlで書かれたcgiが動くサーバがある。ここで、一行スクリプトを動かすにはどうすればよいか。これを考える。

PerlShell.cgi

#!/usr/bin/perl
print "Content-Type: text/html\n\n";
print "<HTML>\n";

[文字操作] パスワードジェネレータ

> perl -e "for(1..32){print chr(int(rand(127-33))+33)}"
th#&KSlwl-e"o@TD/\;n&N3?~uRhyZbQ
> perl -e "for(1..32){print chr(int(rand(127-33))+33)}"
>%wh2WQ_maL6.bi)wBzt$_4wW10t}DBp

わかりにくい。わかりにくすぎて覚えられない。でも破るのは難しそうだ。範囲演算子を使えば何とかなるんじゃないかということで、下のように変えてみたんだな。先に言っとくけどID取得なんかにも使えるんだな。というわけで、実際にやってみた例。たとえばYahoo.comのYahoo! IDは"ID may consist of a-z, 0-9, underscores, and a single dot (.)"出なければいけないそうな。そこで第1段階でこんな感じにしてみたんだな。

> perl -e "unshIFT @F,(a..z),(0..9),'_','.'; print @F;"
abcdefghijklmnopqrstuvwxyz0123456789_.

つまりIDに使える文字(アルファベットの小文字と0から9までの数字とアンダーバーとピリオド)を配列@Fに代入したんだな。次に配列の添え字をランダムに選ぶことによって@Fから1文字選ぶことを適当な回数繰り返せばよいということなんだな。最終的には下のような感じになるんだな。配列の最後の添え字に1足す理由は次のような感じ。rand関数は、ゼロから引数未満の数を返すので、1足さないと配列の最後の内容(ここではピリオド)は出力されないから。

> perl -e "unshift @F,(a..z),(0..9),'_','.'; for(1..70){print $F[int(rand($#F+1))]}"
rnl5_.kenfntt5lujl7l2p_.r0ph5575u0nvclw7f4tsxcf1fysv0grxqmf_.hho4ekzzvn3a
> perl -e "@F=(a..z,0..9,qw(, . _)); print map{$F[rand $#F+1]}(0..16);"
5gdlsyjkvyoda9wos

まぁこういうものは必要になると急激に使用頻度が増えるもので、ふとした瞬間に使いたくなるってこともある。んで、また書いてしまった。

perl -e 'while($i<30){$j=0;while($j<8){$_=chr int rand(127); if(m/[a-z]/){print; $j++;}}print "\n";$i++;}'

これで30個の適当な9文字の文字列を生成してくれる。前よりもコードが長くなっていたり非効率になっている。今回はこれでご勘弁を。

  1. ランダムな文字列を生成するString::Random - Unknown::Programming

環境変数のチェック

> perl -le "map{print qq/$_ $SIG{$_}/}keys %SIG"
> perl -le "map{print qq/$_ $ENV{$_}/}keys %ENV"

範囲演算子でASCII文字を表示する

テストとしてやってみたんだな。範囲演算子は数を一つずつ増やし、各コード値に対応したASCII文字をchr関数で返すんだな。制御文字まで含まれているので出力がおかしくなる。chr関数の引数には10進数も取れるので下の2つは全く同じ出力。どちらも、ASCII文字の0番目から127番目の文字(全部で128文字)を出力しているんだな。

> perl -e "print map{chr}(0..127)"
> perl -e "print map{chr}(0x00..0x7F)"

兎に角人間様がわかる文字だけを出力したい時は下のようにするといい。出力する範囲を換えただけだけど、範囲を変えるときは16進数で書いたほうがわかりやすいかもしれない。

> perl -e "print map{chr}(0x20..0x7e)"
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

人間様が理解できるといえば、あとは改行になるのかなぁ。改行が0x0aなのか0x0dなのか0x0a0x0dなのかは宗教戦争なので適当に0x0aだと考えれば、下のようにかける。

$ perl -e "print map{chr}(0x0a,0x20..0x7e)'
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
  1. [psl]ASCIIコード一覧表

モジュールの存在チェック

> perl -e "use Net::POP3;"

例えばNet::POP3モジュールがインストールされているかを上のように入力してチェックするんだな。これで何にも表示されずにプロンプトに戻ったらインストールされているということなんだな。インストールされていない場合はperlのライブラリディレクトリの下に指定されたモジュールが無いことを示すメッセージが出る。エラーを見ればわかるけど@INCにライブラリを探すディレクトリが収められているので下のようにすれば何がインストールされているかわかる。

> perl -e "print map{join qq/\t/,map{m|([^/]*)$|}<$_/*>}@INC" | more

これだと表示が1画面に収まらないのでパイプ処理でmoreに渡している。気になるなら下のようにもできる。ただこの場合マッチするもの以外は表示されない。例えば下の例では*.pmと*.plは表示されるが、*.txtは表示されない。

> perl -e "print map{join qq/\t/,map{m|([^/]*\.p[ml])$|}<$_/*>}@INC"
abbrev.pl       AnyDBM_File.pm  assert.pl       attributes.pm   attrs.pm
AutoLoader.pm   AutoSplit.pm    autouse.pm      B.pm    base.pm Benchmark.pm
bigfloat.pl     bigint.pl       bigint.pm       bignum.pm       bigrat.pl
bigrat.pm       blib.pm ByteLoader.pm   bytes.pm        bytes_heavy.pl  cacheout
.pl     Carp.pm CGI.pm  charnames.pm    complete.pl     Config.pm       constant
.pm     CPAN.pm ctime.pl        Cwd.pm  DB.pm   DBM_Filter.pm   dbm_filter_util.
pl      diagnostics.pm  Digest.pm       DirHandle.pm    dotsh.pl        Dumpvalu
e.pm    dumpvar.pl      DynaLoader.pm   Encode.pm       encoding.pm     English.
pm      Env.pm  Errno.pm        exceptions.pl   Exporter.pm     fastcwd.pl
Fatal.pm        Fcntl.pm        fields.pm       FileCache.pm    FileHandle.pm
filetest.pm     find.pl FindBin.pm      finddepth.pl    flush.pl        getcwd.p
l       getopt.pl       getopts.pl      hostname.pl     if.pm   importenv.pl
integer.pm      IO.pm   less.pm lib.pm  locale.pm       look.pl Memoize.pm
newgetopt.pl    NEXT.pm O.pm    Opcode.pm       open.pm open2.pl        open3.pl
        ops.pm  overload.pm     perl5db.pl      PerlIO.pm       POSIX.pm
pwd.pl  re.pm   Safe.pm SDBM_File.pm    SelectSaver.pm  SelfLoader.pm   Shell.pm
        shellwords.pl   sigtrap.pm      Socket.pm       sort.pm stat.pl Storable
.pm     strict.pm       subs.pm Switch.pm       Symbol.pm       syslog.pl
tainted.pl      termcap.pl      Test.pm Thread.pm       threads.pm      timeloca
l.pl    UNIVERSAL.pm    utf8.pm utf8_heavy.pl   validate.pl     vars.pm vmsish.p
m       warnings.pm     Win32.pm        XSLoader.pmDB_File.pm   fix_4_os2.pl
LWP.pm  MD5.pm  OLE.pm  Tk.pm   URI.pm  Win32.pm

フィルタリングに正規表現を使用し、マッチしたものだけを表示させている。ディレクトリ直下に含まれるモジュールしか表示されない。map{}でディレクトリの部分をフィルタリングしている。これはreaddirを使わないで書こうとしたためだ。opendir readdir closedirとすると1行スクリプトと言うには長すぎるからね。

[処理制御] 10秒間処理を停止する

もっとも問題なのはこの手の処理がWindowsに標準でついていないことじゃないかな。昔はプロセスの起動と終了を監視するプログラムを作ったもんだ。まぁそんなに厳密にならなくていい場合はsleep文で済ませてしまえばいい。perlの一行スクリプトでそんなことができるなんていい時代になったもんだ。

> perl -e "sleep(10)"

単独ではほとんど意味ない。でも、Windowsのバッチファイル中で使うと少しいいことあるかもしれない。なぜなら、バッチファイルの処理分岐は別ウィンドウで起動されるプログラムの終了コードを判別しないからだ。つまり、バッチプログラムはMS-DOSプロンプトに処理結果を返すプログラムについては、終了後に先のプログラムの結果を利用することができるが、そうでない場合はただのアプリケーションランチャとしてしか使えない。したがってアプリケーションAが終了するまでに十分な時間を上のように書くことでsleepさせて、その後に別のアプリケーションBを起動することもできる。

思いついた使い方としてはラーメンタイマーなんだな。下のように書いてみた。

> perl -le "for(1..6){sleep(3); print scalar localtime;}print qq/\a/x10;"
Sun Mar 26 01:57:20 2006
Sun Mar 26 01:57:22 2006
Sun Mar 26 01:57:26 2006
Sun Mar 26 01:57:29 2006
Sun Mar 26 01:57:31 2006
Sun Mar 26 01:57:35 2006

100分の1秒までこだわりたいなら次のようにもできる。カップラーメン作るのに100分の1秒までこだわらなくても上の例よりも短くかけてほとんど同じ効果をもたらすのでこちらの方がいいかもしれない。こちらはプログラム的にはあまりよろしくないが、3分待つにはどうでもいいことだ。よろしくないのはprint文に続くtimesをスカラーとして評価する時に無理やり0を足し算することでこれをなしている点。本来ならばscalar timesとするべきかも。でもこの方が短いので文字数制限のあるコマンドプロンプトではそのほうがいいかも。

> perl -le "for(1..6){sleep(3); print 0+times;}print qq/\a/x10;"
3.08
6.1
9.07
12.09
15.11
18.07

あわせ技でこんなのもありかな。timesのスカラー評価に同じ手法を使った。

> perl -le "for(1..6){sleep(3); print join' ',map{scalar localtime $_}(time,times+15*60*60);}print qq/\a/x10;"
Sun Mar 26 02:48:10 2006 Fri Jan  2 00:00:03 1970
Sun Mar 26 02:48:13 2006 Fri Jan  2 00:00:06 1970
Sun Mar 26 02:48:16 2006 Fri Jan  2 00:00:09 1970
Sun Mar 26 02:48:19 2006 Fri Jan  2 00:00:12 1970
Sun Mar 26 02:48:22 2006 Fri Jan  2 00:00:15 1970
Sun Mar 26 02:48:25 2006 Fri Jan  2 00:00:18 1970

上の例だと時間が決まっているばあいはいいけど、時計のようには使えない。時計にするだけなら簡単なのでここでは何秒かに1回新しく表示を切り替える時計を作ってみようと思う。

> perl -le "while(sleep 3){print scalar(localtime).qq/\a/x2;}"
Mon Mar 27 21:50:16 2006
Mon Mar 27 21:50:19 2006
Mon Mar 27 21:50:22 2006
Mon Mar 27 21:50:25 2006
Terminating on signal SIGINT(2)

3秒に1回新しい時刻を出力している。10分に1回で十分なときはsleep 600とすればいい。ついでにビープ音もならしてている。10分に1回づつ何かするけどコンピュータから目を離しておきたい場合なんかにも音で知らせてくれればありがたい。常にディスプレイとにらめっこする必要が無いからうれしい。でもストップウォッチ的に使うには多少問題外があるとおもう。精度も悪いし、操作性もよくない。

何行にも表示されてうっとうしい場合はフラッシュ($|=1)とキャリッジリターンの出力(\x0d)で解決。\rとかを使うとプラットフォームごとに異なるスクリプトにしないと使えなくなるかも。

$ perl -e '$|=1; while(sleep(1)){print scalar(localtime)."\x0d"}';

[時刻表示] シンプルな時刻表示によるラーメンタイマー

30秒ごとに時刻表示しているんだな。下のラーメンタイマーよりもずっとシンプルでわかりやすいと思うんだな。キモはlocaltimeをスカラ変数で評価すること。わざわざ関数を作らなくてもいいのでこの技は一行スクリプト以外でも使える。できるまでの間コンソールから目を離せないというのは不便なので、最後にビープ音を鳴らしているんだな。print文で\aを出力するとビープ音がなるんだな。

> perl -le "for(-6..0){print scalar localtime; $_ ? '' : last; sleep 30;} print qq/\a/x3;"
Mon Mar 27 22:55:00 2006
Mon Mar 27 22:55:30 2006
Mon Mar 27 22:56:00 2006
Mon Mar 27 22:56:30 2006
Mon Mar 27 22:57:00 2006
Mon Mar 27 22:57:30 2006
Mon Mar 27 22:58:00 2006

でもラーメン食べようと思って、コンソールに向かって上を入力し始めたら回りは引くだろうな。ラーメンタイマーだけならこれで十分なんだけどさ。

> perl -e "sleep 180; print qq/\a/x3;"

[時刻表示] 今何時?

とりあえず今何時か知りたいとき下のようにするんだな。localtimeを素で理解できる人はすごいと思うけど、多分そんな人はいないだろうな。でもlocaltimeをスカラー変数で評価すれば、僕にもわかる文字列で結果を返してくれるんだな。例えば下のように。

>perl -e "print scalar localtime;"
Tue Feb 14 23:36:13 2006

Windowsは上手いこと現在時刻を表示する方法がなかったからこれは結構重宝すると思うんだな。とにかく重要なことはscalar(localtime($_))として、$_に適当な自然数を代入することなんだな。上の場合はlocaltimeの引数としてtimeが与えられているんだな。だから、下のようにもできるんだな。

>perl -e "print scalar localtime 0;"
Thu Jan  1 09:00:00 1970

localtimeの引数はこの時刻からの経過秒を示すんだな。

[文字列出力] 変数を含む複数行文字列を標準出力に出力する普遍的な方法

つまりどうすればunixのechoコマンドがエミュレートできるかと言うことである。シングルコーテーションもダブルコーテーションもperlが括られた内容を文字列ですよと言って解釈するためのタグ付けのようなものに過ぎない。またPerlはややこしくなるのを防ぐためにシングルコーテーションとダブルコーテーションの代替手段を備えている。それは"STRING"の代わりにqq/STRING/、'STRING'の代わりにq/STRING/とすることだ。これに対してcommandプロンプトはダブルコーテーションを読み替えられるほど柔軟にできてはいない。だから下の2つは全く同じ結果を返す。

> perl -e "print 'Hello World!!';"
Hello World!!
> perl -e "print q/Hello World!!/;"
Hello World!!

これだけだと実行後の改行も入らないし普遍的とはいえない。では改行や変数の内容を含めるにはどうするか。perlスクリプトを外部ファイルから呼び出す場合には、改行はprint "\n"、変数の内容はpeint "$Hello"、とする。したがって、先の変換則に従えば下のようにかける。

> perl -e "$Hello='Hello'; print qq/$Hello\nWorld!!\n/;"
Hello
World!!

普遍的と銘打つにはもう一声必要だ、それはqq//やq//で囲まれる文字列の中に/が含まれてしまう場合だ。例えば下のような場合、期待した結果を返さない。なぜならperl君はいったいどこまでがprint文の引数なのかわからなくなってしまうからだ。ちなみにsyntax errorとは何かと言うことについても講釈せねばなるまい。一般にコンパイラってのは字句解析、構文解析と処理を進めていく(覚え方は"自分が1番"でしたっけ?)けど、このときの最初のステップはクリアしたけど2番目の構文解析で躓いたために、以降の作業が行えませんでしたと言うことである(日本人の書く英字論文は査読の段階でsyntax errorが多発するらしい、単語は書いてあるけど構文の使い方がおかしいからなに言ってんだかわかんない状況。)。つまり、print文の引数指定の方法(引数の使い方)がおかしいと言うことである。少なくとも僕はそう思っている。

> perl -e "print q/http://www.google.com/;"
syntax error at -e line 1, near "/;"
Execution of -e aborted due to compilation errors.

これはperl一般にいえることだが、/で囲むようにされている場合/が区切り文字(デリミタ)として使用されている。だから囲まれた内容に区切り文字を含めてはいけない。そんな場合は区切り文字自体を変えてしまえばいいたとえば下のようにかけば期待した結果が得られる。

> perl -e !print q!http://www.google.com!;"
http://www.google.com/

だからと言って下のようにはかけない。理由は先に述べた通り区切り文字!が囲まれた文字列Hello World!!の中に含まれてしまうからだ。

> perl -e "print q!Hello World!!!;"
syntax error at -e line 1, near "q!Hello World!!"
Execution of -e aborted due to compilation errors.

上に述べたようにこれを解決するには区切り文字を変更してしまえばいい。つまり下のような感じにすると言うことだ。区切り文字に使える文字は様々なので内容に適した物を選べばいい。

> perl -e "print q#Hello World!!#;"
Hello World!!

これらの心はただ一つ。-eオプションの後に続く引数はダブルコーテーションで囲み、ダブルコーテーションで囲まれた文字列ではダブルコーテーションもシングルコーテーションも使わずにqq//やq//で置き換える、内容に区切り文字/が含まれる場合は区切り文字字体を変更する、ということである。

[文字列出力] 最も基本的な方法

最もベーシックなやり方なんだな。これをコンパイラは下のように理解しているんだな。

print 'ww';

したがって、変数展開やメタ文字解釈は行われないんだな。例えば、変数やメタ文字を含めても下のようになってしまうんだな。

> perl -e "$xx = yy; print 'ww$xx\n';"
ww$xx\n
>

コンパイラからすれば、下のような文を処理しているわけだから、当然といえば当然なんだな。

print 'ww$xx\n';

なぜだかわからん

> perl -e "$|=1;while(1){$i++;if($t!=times){print qq#$i\t$t\t#;$i=0;$t=times;}}"
1               78      0.16    478     0.22    591     0.27    579     0.33
369     0.38    790     0.44    426     0.49    308     0.55    284     0.6
658     0.66    283     0.71    500     0.77    519     0.82    812     0.88
259     0.93    470     0.99    590     1.04    720     1.1     562     1.15
591     1.21    638     1.26    797     1.32    798     1.37    287     1.43
812     1.48    412     1.54    432     1.59    691     1.65    536     1.7
442     1.76    695     1.81    604     1.87    645     1.92    517     1.98
590     2.03    370     2.09    754     2.14    600     2.2     252     2.25
728     2.3     602     2.36    467     2.41    641     2.47    589     2.52
335     2.58    251     2.63    733     2.69    691     2.74    784     2.8
404     2.85    329     2.91    599     2.96    198     3.02    554     3.07
499     3.13    141     3.18    378     3.24    823     3.29    294     3.35
201     3.4     650     3.46    698     3.51    341     3.57    674     3.62
285     3.68    656     3.73    721     3.79    594     3.84    534     3.9
769     3.95    531     4.01    455     4.06    780     4.12    796     4.17
293     4.23    411     4.28    822     4.34    496     4.39    788     4.45
825     4.5     136     4.56    609     4.61    551     4.67    492     4.72
518     4.78    417     4.83    754     4.89    495     4.94    346     5
518     5.05    502     5.11    601     5.16    419     5.22    739     5.27
526     5.33    703     5.38    566     5.44    ^C
634     5.49    Terminating on signal SIGINT(2)
>

[丸め込み誤差]コーシー判定で発散することが自明の数列も計算精度で収束する

もちろんコーシー判定の結果が正しいのである。しかし、16桁の精度しか出ないので収束してしまう。例えば、一般項が1/iの数列(a_i=1/i)の無限級数はコーシー判定より正の無限大に発散する。ただし、第n部分和をコンピュータで単純にn回足し算を行って求めるとおかしなことが起こる。次の状態を考える。i=10^{16}=>a_i=10^{-16}このとき第n部分和の正確な値はわからない、しかし、少なくとも1以上の値である。なぜなら、数列の初項が1であり、各項は正の値のみを取るから。第10^{16}-1部分和で取られるであろう最大の精度はコンピュータの計算精度と同じ16桁、言い換えれば、小数点以下15桁目までが出力される。これに第10^{16}項目が足されるわけだが、第10^{16}項目は小数点以下16桁目に始めて0で無い数字が現れる。したがって、足し算しても第10^{16}部分和に影響を及ぼさない。したがって第10^{16}部分和と第10^{16}-1部分和は等しいとして出力される。ゆえに、第10^{16}-1部分和以降の部分和はいつまでたっても第10^{16}-1部分和と同じ値となる。したがってコンピューターで第n部分和を計算させるとその値は収束してしまう。

  | 1/3                   => 0.333333333333333
+ | 1/10^{16}             => 0.0000000000000001
  +---------------------------------------------
    (3+10^{16})/3*10^{16} => 0.333333333333333
> perl -wle "while(1){$S+=(++$i)**-1; print join qq#\t#,($i,$S,log($i),(times)[0]) if $i%10**6==0;}"
1000000 14.392726722865 13.8155105579643        6.15
2000000 15.085873653425 14.5086577385242        15.71
3000000 15.4913386781999        14.9141228466324        22.03
4000000 15.7790207089847        15.2018049190842        29.99
5000000 16.0021642352986        15.4249484703984        38.61
6000000 16.1844857754261        15.6072700271923        47.13
7000000 16.3386364433484        15.7614207070196        57.12
8000000 16.4721678270444        15.8949520996441        68.71
9000000 16.5899508557555        16.0127351353005        75.36
10000000        16.6953113658573        16.1180956509583        85.74
11000000        16.7906215411161        16.2134058307626        93.43
12000000        16.8776329143183        16.3004172077523        103.64
13000000        16.9576756187865        16.3804599154258        111.61
14000000        17.0317835881946        16.4545678875795        119.02
15000000        17.1007764573005        16.5235607590665        127.15
16000000        17.1653149763541        16.5880992802041        139.46
17000000        17.2259395963306        16.6487239020205        147.09
18000000        17.2830980085365        16.7058823158604        152.8
19000000        17.3371652283451        16.7599495371307        161.87
20000000        17.3884585214171        16.8112428315183        167.8
Terminating on signal SIGINT(2)
>

[ダイヤモンド演算子]dirの代替

> perl -we "print map{$_.\"\t\".scalar(localtime((stat)[9])).\"\n\"}('.','..',<./*>)"
.       Wed Aug 16 17:05:44 2000
..      Wed Aug 16 16:35:38 2000
./a.bat      Sat Feb 18 19:30:08 2006
./a.pl       Tue Feb 14 02:35:22 2006
./a.txt      Sat Feb 18 19:35:58 2006
./b.txt      Sat Feb 25 00:18:58 2006
>

何でもかんでもmap{}を使えばよいと言うものでもない。map{}を使うよりも美しい場合もあると思う。下のほうが美しいSolutionだとは思いませんか。

> perl -wle "foreach('.','..',<./*>){print qq/$_\t/.scalar(localtime((stat)[9]))}"

ストップウォッチ

ラップタイムはPause/Break、再度時間を進める場合はEnter、停止はCtrl+C

> perl -le "while(1){print scalar times;}"

perl - [スカラー評価] 現在時刻の表示

素直にdateコマンド使えって話ですけど、もんだいはwindowでそうやる方法がないってこと、単純に現在時刻表示して、終了。ただそれだけが windows ではできない。これはすごくストレスなんだよ。

> perl -we "print scalar(localtime);"
Fri Feb 17 21:20:46 2006
C:\>

perl - [スカラー評価] 現在時刻を1秒単位で表示

現在時刻を表示する方法がわかったら、これを1秒単位にしてみる。行を削除できればデジタル時計になるんだけど、それは無理でしょ、一回printバッファから出た情報をどうやって書き換えればいいの。

> perl -wle "for(1..10){print scalar(localtime); sleep 1;}"
Fri Feb 17 22:25:51 2006
Fri Feb 17 22:25:52 2006
Fri Feb 17 22:25:53 2006
Fri Feb 17 22:25:54 2006
Fri Feb 17 22:25:55 2006
Fri Feb 17 22:25:56 2006
Fri Feb 17 22:25:57 2006
Fri Feb 17 22:25:58 2006
Fri Feb 17 22:25:59 2006
Fri Feb 17 22:26:00 2006
>

[スカラー評価]デジタル時計

> perl -wle "while(1){system cls; print scalar(localtime); sleep 1;}"
Fri Feb 17 22:31:25 2006
Terminating on signal SIGINT(2)
>

[条件判定]if文の代替

3番目の例について言えば、条件判定にif文を使うのは遅くなる原因だそうな。この場合、\$aがゼロか、\$bがゼロか、\$cがゼロか、という判定をif文で書くのではなく、演算子を使って記述した方が早いということ。変数を||でつないだ場合は左から評価を始めて最初に現れた0でない数字を返す。変数を&&でつないだ場合は左から評価を始めて最初に現れた0を返す。

> perl -wle "print 0||3||1; print 3||1||0; print 1||3||0;"
3
3
1
> perl -wle "(\$a,\$b,\$c)=(0,3,1); \$d=\$a||\$b||\$c; print \$d;"
3
> perl -wle "\@a=map{int rand 3}(0..20); print \@a; print eval(join'||',\@a);"
222112121101120001212
2
> perl -wle "\@a=map{int rand 3}(0..20); print \@a; print eval(join'&&',\@a);"
210100122012201210021
0
>

単体ではほとんど意味をなさないが、このような条件判定は初期値の設定に使える。例えば、引数にディレクトリを取り、そのディレクトリの内容を表示するプログラムを考えてみる。内容を表示したいディレクトリ$dirに下のように代入させることができる。

$dir = $ARGV[0] || './';

ただし、この場合は$ARGV[0]の内容が0や空文字列だった場合には上手くいかない。言い換えれば、0というディレクトリの内容を表示することはできないということだ。もちろん引数に'./0'と指定すれば問題はないが、それではユーザビリティに欠けるというものだ。そんな場合は下のようにするとよいだろう。言い換えれば、引数が指定されていてなおかつ引数の内容が空文字列でない場合、引数をそのまま$dirへ代入、これ以外の場合は、カレントディレクトリを$dirに代入する、ということだ。こうすれば引数に0を指定されたり、コマンドの後に無駄な空白をつけたおかげで引数が定義されてはいるものの意味のない引数(空文字列)だった場合にもまともな結果を返すことができる。

$dir = (defined $ARGV[0] && $ARGV[0] ne '') ? $ARGV[0] : './';

[perl] Linux 上の Perl で書いた 1 行スクリプト

ここまでは Activeperl を対象に 1 行スクリプトを書いてきたが、これからは Linux で動く Perl もしくは Cygwin 上で動く Perl で 1 行スクリプトを書こうと思う。

[linux] 総和の方向で結果が異なるのは仕方ない。

その理由はやはり丸め込み誤差だろうな誤差を小さくするためにはどうすればいいのだろう。

$ perl -wle 'for($i=1;$i<=10**8;$i++){$S+=$i**-1;} print join qq#\t#,($i,$S,(times)[0]);'
100000001       18.9978964138477        103.31
$ perl -wle 'for($i=10**8;$i>=1;$i--){$S+=$i**-1;} print join qq#\t#,($i,$S,(times)[0]);'
0       18.9978964138532        157.2
$ 

判定と 2 重 for でどっちが早いのか

これは宿題

たとえば、for ブロックの中で、10 回に 1 回出力したいとき、ブロックの最後に if( $i % 10 == 0){print;} とかすると思う。これはよく紹介されている手法だが、スピード的にはどうかと思った。だって、100 回ループさせたら、100 回チェックが行われるわけで、そのうちの 90 回は出力されない。もしループ回数が多くなって、より出力の回数が減ったら、さらに無駄な判断を繰り替えすことになる。もったいなくないか。

$  perl -wle 'for($i=1;$i<=10**7;$i++){$S+=$i**-1; if($i%10**6==0){ print join qq#\t#,($i,$S,(times)[0]);} }'
1000000 14.3927267228648        1.25
2000000 15.0858736534248        2.49
3000000 15.4913386781997        3.73
4000000 15.7790207089843        4.98
5000000 16.0021642352982        6.22
6000000 16.1844857754256        7.47
7000000 16.338636443348 8.71
8000000 16.4721678270439        9.96
9000000 16.5899508557549        11.2
10000000        16.6953113658567        12.46
$ perl -wle 'for($j=0;$j<10;$j++){ $s=1+$j*10**6; $e=($j+1)*10**6; for($i=$s;$i<=$e;$i++){$S+=$i**-1;} print join qq#\t#,($i,$S,(times)[0]);}'
1000001 14.3927267228648        0.96
2000001 15.0858736534248        1.92
3000001 15.4913386781997        2.88
4000001 15.7790207089843        3.84
5000001 16.0021642352982        4.81
6000001 16.1844857754256        5.77
7000001 16.338636443348 6.73
8000001 16.4721678270439        7.68
9000001 16.5899508557549        8.64
10000001        16.6953113658567        9.6
$ cat test.pl
#!/usr/bin/perl
$a = 100;
$b = 10**6;
$ab= $a*$b;
for($k=0;$k<1000;$k++){
        $c = (times)[0];
        for($i=0; $i<$a; $i++){
                print ((times)[0]-$c);
                print "\t";
                for($j=0; $j<$b; $j++){
                        #$a_j = $i * $b + $j;
                        $a_j++;
                        #print "$a_j ";
                }
        }
        $c = (times)[0];
        for($i=0; $i<$ab; $i++){
                if(0==$i % $b){
                        print ((times)[0]-$c);
                        print "\t";
                }
        }
        print "\n";
}
$ nohup nice perl test.pl > test.dat &
$ cat test.plt
#!/usr/bin/gnuplot
!perl -alne 'map{$S[$_]+=@F[$_]}(0..$#F);$i++; END{print join"\n",map{$_/$i}@S;}' test.dat > test2.dat
set xlabel "output setp [times]";
set ylabel "CPU time [sec]";
set grid
set terminal svg
set output "test.svg"
set key left top
plot    "<perl -ne 'print if 1 .. 100' test2.dat" title "if()",\
        "<perl -ne 'print if 101 .. 200' test2.dat" title "double for()"
CPU 時間とステップ数

改行コードの変換 (nkf の代替)

nkf は改行コードと文字コードの変換によく使う。unix、mac、windows それぞれのシステムにおける改行コードは決まっているので、それらにおける改行コードが具体的に何か知らなくても下のような感じで使えば、改行コードの変換が出来る。

$ nkf --unix hoge.c > tmp.c
$ nkf --mac hoge.c > tmp.c
$ nkf --windows hoge.c > tmp.c

このようにすることで、改行コードを unix や mac や windows のものにして hoge.c の内容を tmp.c にリダイレクトできる。これを perl で代替するには (それぞれのシステムの改行コードを具体的に知らなければならないが) 下のようになる。

$ perl -pe 's/\x0D\x0A|\x0D|\x0A/\x0A/g;' hoge.c > tmp.c
$ perl -pe 's/\x0D\x0A|\x0D|\x0A/\x0D/g;' hoge.c > tmp.c
$ perl -pe 's/\x0D\x0A|\x0D|\x0A/\x0D\x0A/g;' hoge.c > tmp.c

もらったファイルの改行コードで悩むことって、*nix 使っている限りあまりないのだけれど、自分のシステムの改行コードに合わせるには下のようにする。置換の変換先を \n つまりシステム標準の改行コードにするということだ。

$ perl -pe 's/\x0D\x0A|\x0D|\x0A/\n/g;' hoge.c > tmp.c

cpan にモジュールとかありそうなものだけど、まぁ手書きでも出来ないこともない。重要なのは置換元の並び順は先頭から評価されるため、置換前の改行コードの並び順が \x0D\x0A|\x0D|\x0A の順番でないとうまく動作しないということ。-p で print; 付きの各行読み込み。s// で置換、Win か Mac か Unix の改行コードを Unix の改行コードに。読み込みは hoge.c、標準出力をリダイレクトして tmp.c に。リダイレクト先と入力を別にしておくことに注意。リダイレクト先を hoge.c にしてしまうと hoge.c の内容がクリアされてしまう。

次のような 3 つの入力を置換することを考える。

in:
This is a pen.\x0D\x0A
This is a pen.\x0D
This is a pen.\x0A

このとき、それぞれの変換は次のように進んでいると思われる。変換の評価ポイントが一文づつずれていく。評価ポイント上の文字が変換前の文字列とマッチするか評価する。変換前の文字列のプライオリティは \x0D\x0A が最高なので、評価ポイント上の文字 + 次の 1 文字でまず評価され、この文字列が \x0D\x0A でない場合、評価ポイント上の文字を評価する。

This is a pen.\x0D\x0A の場合
|------------*####
This is a pen.\x0D\x0A  -->  This is a pen.\x0D\x0A
|------------*
This is a pen.\x0D\x0A  -->  This is a pen.\x0D\x0A
|-------------****####
This is a pen.\x0D\x0A  -->  This is a pen.\x0A
|-----------------*#
This is a pen.\x0A      -->  This is a pen.\x0A
This is a pen.\x0D の場合
|------------*####
This is a pen.\x0D      -->  This is a pen.\x0D
|------------*
This is a pen.\x0D      -->  This is a pen.\x0D
|-------------****#
This is a pen.\x0D      -->  This is a pen.\x0D
|-------------****
This is a pen.\x0D      -->  This is a pen.\x0A
|-----------------*#
This is a pen.\x0A      -->  This is a pen.\x0A
This is a pen.\x0A の場合
|------------*####
This is a pen.\x0A      -->  This is a pen.\x0A
|------------*
This is a pen.\x0A      -->  This is a pen.\x0A
|-------------****#
This is a pen.\x0A      -->  This is a pen.\x0A
|-------------****
This is a pen.\x0A      -->  This is a pen.\x0A
|-----------------*#
This is a pen.\x0A      -->  This is a pen.\x0A

こんな感じだ。最初の評価が 2 文字ということがポイントだと思う。\x0D の 1 文字の評価のプライオリティが最高だと、\x0D\x0A がまず \x0A\x0A に変換されてしまい、このときの変換ポイントが次の評価で次に進み、期待通りの結果が得られない。

s/\x0D|\x0D\x0A|\x0A/\x0A/g; の場合
out:
This is a pen.\x0A\x0A
This is a pen.\x0A
This is a pen.\x0A

今回のネタは、nkf が導入されていないシステムで改行コードを変更しようとしたことが発端である。そもそも nkf は日本生まれなので、今回作業に躓いたサーバでは管理者が日本人でなかったためか nkf が導入されていなかった。でも、海外の人はどうやって改行コードの変換を行っているのだろう、ところ変われば品変わるのかな。

自分しか編集しないファイルの場合は改行コードを気にすることはほとんどないが、だれかとやり取りする場合は結構気にする。全然改行のないテキストをメモ帳で見て、ああこの人はどうやってテキストを編集しているのだろうかと本気で悩んだことがあった。Web を作ったときにアップロードの前後でファイルサイズが異なっていることがかなり気になったことがあった。そんなこともあったということで。

文字コードの変更

結論的には nkf 入れるのがもっとも素直でよいと思う。しかし nkf が無いような場合、iconv で文字コードを変換するのだけれど、iconv には nkf -guess のような文字コード判定をしてくれるような機能が付いてはいない。そのため、たくさんの文書を一気に変更する場合だと使いにくい。nkf 無でも perl 位はあるだろう、ということで perl の 1 行スクリプトを書いてみた。まずは適当に作ったファイル hoge.txt を euc-jp に変換してみる。

$ perl -MEncode -MEncode::Guess -pe '$_=encode("euc-jp",decode("Guess",$_));' hoge.txt > hoge_euc-jp.txt
$ nkf -guess hoge_euc-jp.txt
EUC-JP

たしかに euc-jp に変換されているが、この 1 行スクリプトは入力ファイルが euc-jp の場合にはうまく変換されない。

$ nkf -guess hoge.txt
UTF-8
$ perl -MEncode -MEncode::Guess -pe '$_=encode("utf8",decode("Guess",$_));' hoge_euc-jp.txt > hoge_euc-jp_utf8.txt
No appropriate encodings found! at /usr/local/lib/perl/5.8.8/Encode.pm line 170

その理由は Encode:Guess が "By default, it checks only ascii, utf8 and UTF-16/32 with BOM." というルールになっているからだ。つまり、入力ファイルの文字コードが euc-jp の場合を想定して "Guess" が行われないため、文字コード判定に失敗してしまうわけだ。これを回避するためには Encode::Guess の set_suspect メソッドで入力ファイルの文字コードとして可能性のあるものを挙げておく。

$ perl -MEncode -MEncode::Guess -pe 'BEGIN{Encode::Guess->set_suspects(qw/euc-jp/);} $_=encode("utf8",decode("Guess",$_));' hoge_euc-jp.txt > hoge_euc-jp_utf8.txt
$ nkf -guess hoge_euc-jp_utf8.txt
UTF-8

これで適切な変換が行われた。当然ながらこのままでは shiftjis とか iso-2022-jp とかの場合にはやはりエラーが出る。入力ファイルとして日本語という足かせを付けて置くならば、可能性のある文字コードは euc-jp shiftjis 7bit-jis iso-2022-jp 位を考えれば十分だろう。set_suspect メソッドの引数にこれらの配列を渡せば「入力ファイルが日本語で書かれた文書」の文字コードの変更を自動的に行う 1 行スクリプトとなるはずだが、そうは問屋がおろさない。

$ perl -MEncode -MEncode::Guess -pe 'BEGIN{Encode::Guess->set_suspects(qw/euc-jp shiftjis 7bit-jis iso-2022-jp/);}  $_=encode("utf8",decode("Guess",$_));' hoge_euc-jp.txt > hoge_euc-jp_utf8.txt

例えば下のようなスクリプトを使って、文字コードの相互変換テストしてみるとやはりエラーが出る。

$ cat hoge.txt
日本語入力のテスト
$ cat enctest.sh
for to in utf8 euc-jp shiftjis 7bit-jis iso-2022-jp
do
        e="\$_=encode('${to}',decode(\"Guess\",\$_));"
        #e="print '${to}';"
        #perl -le "$e"
        perl -MEncode -MEncode::Guess -pe "$e" hoge.txt > hoge_${to}.txt
        echo -n "hoge_${to}.txt: "
        nkf -guess hoge_${to}.txt
done
for from in utf8 euc-jp shiftjis 7bit-jis iso-2022-jp
do
        echo -n "hoge_${from}.txt: "
        nkf -guess hoge_${from}.txt
        for to in utf8 euc-jp shiftjis 7bit-jis iso-2022-jp
        do
                #e="'\$_=encode(\"${to}\",decode(\"Guess\",\$_));'"
                e="BEGIN{Encode::Guess->set_suspects(qw/euc-jp shiftjis 7bit-jis iso-2022-jp/);} \$_=encode('${to}',decode(\"Guess\",\$_));"
                #perl -MEncode -MEncode::Guess -pe $e hoge_${from}.txt > hoge_${from}_${to}.txt
                perl -MEncode -MEncode::Guess -pe "$e" hoge_${from}.txt > hoge_${from}_${to}.txt
                echo -n "hoge_${from}_${to}.txt: "
                #echo $e
                nkf -guess hoge_${from}_${to}.txt
        done
done
exit
$ sh ./enctest.sh
hoge_utf8.txt: UTF-8
hoge_euc-jp.txt: EUC-JP
hoge_shiftjis.txt: Shift_JIS
hoge_7bit-jis.txt: ISO-2022-JP
hoge_iso-2022-jp.txt: ISO-2022-JP
hoge_utf8.txt: UTF-8
hoge_utf8_utf8.txt: UTF-8
hoge_utf8_euc-jp.txt: EUC-JP
hoge_utf8_shiftjis.txt: Shift_JIS
hoge_utf8_7bit-jis.txt: ISO-2022-JP
hoge_utf8_iso-2022-jp.txt: ISO-2022-JP
hoge_euc-jp.txt: EUC-JP
hoge_euc-jp_utf8.txt: UTF-8
hoge_euc-jp_euc-jp.txt: EUC-JP
hoge_euc-jp_shiftjis.txt: Shift_JIS
hoge_euc-jp_7bit-jis.txt: ISO-2022-JP
hoge_euc-jp_iso-2022-jp.txt: ISO-2022-JP
hoge_shiftjis.txt: Shift_JIS
hoge_shiftjis_utf8.txt: UTF-8
hoge_shiftjis_euc-jp.txt: EUC-JP
hoge_shiftjis_shiftjis.txt: Shift_JIS
hoge_shiftjis_7bit-jis.txt: ISO-2022-JP
hoge_shiftjis_iso-2022-jp.txt: ISO-2022-JP
hoge_7bit-jis.txt: ISO-2022-JP
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_utf8.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_euc-jp.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_shiftjis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_7bit-jis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_iso-2022-jp.txt: ASCII
hoge_iso-2022-jp.txt: ISO-2022-JP
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_utf8.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_euc-jp.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_shiftjis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_7bit-jis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_iso-2022-jp.txt: ASCII

つまり、入力ファイルを見ただけでは 7bit-jis か iso-2022-jp なのかわからんといわれているわけだ。これはそんなに不思議なことではない。というのも、hoge.txt には半角カタカナは含まれないため、それを変換した hoge_7bit-jis.txt にも hoge_iso-2022-jp.txt にも含まれない、従って「JIS X 0201 片仮名」で半角カタカナに割り当てられるようなビット列が含まれない。また、7bit-jis の「JIS X 0201 片仮名」(いわゆる半角カタカナ) を iso-2202-jp では定義していない。そのため、これらのいわゆる半角カタカナの含まれないファイルの文字コードを guess しても 7bit-jis と iso-2022-jp の区別が付かないのである。

では、半角カタカナの含まれる utf8 のファイルを先のスクリプトを通して iso-2022-jp に変換するとどうなるのか。この結果は半角カタカナが全角カタカナに変換される。また、7bit-jis に変換するとどうなるか。この結果は半角カタカナの部分がおかしなことになってしまった。うーんよくわからんな。テストしてみたコードは下のような感じ。

$ cat hoge.txt
日本語入力のテスト
ニホンゴニュウリョクノテスト
$ sh enc.sh
hoge_utf8.txt: UTF-8
hoge_euc-jp.txt: EUC-JP
hoge_shiftjis.txt: Shift_JIS
hoge_7bit-jis.txt: ISO-2022-JP
hoge_iso-2022-jp.txt: ISO-2022-JP
hoge_utf8.txt: UTF-8
hoge_utf8_utf8.txt: UTF-8
hoge_utf8_euc-jp.txt: EUC-JP
hoge_utf8_shiftjis.txt: Shift_JIS
hoge_utf8_7bit-jis.txt: ISO-2022-JP
hoge_utf8_iso-2022-jp.txt: ISO-2022-JP
hoge_euc-jp.txt: EUC-JP
shiftjis or euc-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_euc-jp_utf8.txt: UTF-8
shiftjis or euc-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_euc-jp_euc-jp.txt: EUC-JP
shiftjis or euc-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_euc-jp_shiftjis.txt: Shift_JIS
shiftjis or euc-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_euc-jp_7bit-jis.txt: ISO-2022-JP
shiftjis or euc-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_euc-jp_iso-2022-jp.txt: ISO-2022-JP
hoge_shiftjis.txt: Shift_JIS
hoge_shiftjis_utf8.txt: UTF-8
hoge_shiftjis_euc-jp.txt: EUC-JP
hoge_shiftjis_shiftjis.txt: Shift_JIS
hoge_shiftjis_7bit-jis.txt: ISO-2022-JP
hoge_shiftjis_iso-2022-jp.txt: ISO-2022-JP
hoge_7bit-jis.txt: ISO-2022-JP
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_utf8.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_euc-jp.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_shiftjis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_7bit-jis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_7bit-jis_iso-2022-jp.txt: ASCII
hoge_iso-2022-jp.txt: ISO-2022-JP
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_utf8.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_euc-jp.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_shiftjis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_7bit-jis.txt: ASCII
7bit-jis or iso-2022-jp at /usr/local/lib/perl/5.8.8/Encode.pm line 170
hoge_iso-2022-jp_iso-2022-jp.txt: ASCII

とりあえず今日はここまで。なんだかよくわからないことになってしまった。とりあえず guess 機能のチェックからはじめよう。

Hello world と表示させる

さて、最も単純に考えると下のようになる。

$ perl -e 'print "Hello World!!\n";'

もう少し短くしたければ -l オプションで print 文と fprint 文の最後に \n を自動的に付加させる。下の 2 つは同じことである。

$ perl -le 'print "Hello World!!";'
$ perl -e '$\="\n"; print "Hello World!!";'

シェルの違いシステムの違いでスクリプトが動かなくなることがたまにある。これを解決するために文字列リテラル中の半角スペースの代わりに \x20 を使う。\x20 は ASCII コードにおける半角スペース (SP) を 16 進数であらわした (20) もの。

$ perl -le 'print "Hello\x20World!!";'

今回のネタはシステムの違いをいかに吸収して 1 行スクリプトを書くかについて考えているときに思い浮かんだ。

[rename] ファイルの名前を一括変換

いや、あえて Perl をつかって表現する必要はどこにもないのだけれど、そんなことも可能だよということで。おそらくこのシリーズはネタ切れのタイミングで続くな。まずは普通にやってみる。mv コマンドのほうが短いし、はっきり言ってメリットは無い。

$ mv hoge.txt hage.txt
$ perl -le 'print rename("hoge.txt", "hage.txt");'

このままだと面白くない、File::Rename モジュールを使おう。無い場合は CPAN から導入してくれと管理者にでも頼んでくだされ。まずは上と同じことをモジュールを使ってやってみる。

$ perl -MFile::Rename -le 'print File::Rename::rename("hoge.txt", "hage.txt");'

File::Rename モジュールはただの use 文では組み込み関数 CORE::rename をオーバーライドしてくれないので、File::Rename モジュールの rename 関数を呼ぶ場合には枕詞をつけて明示的に rename 関数を呼ばなければならない。もし、下のように書いた場合は CORE::rename を使っていることになる。

$ perl -MFile::Rename -le 'print rename("hoge.txt", "hage.txt");'

rename 組み込み関数を qw() でオーバーライドしてみる。オーバーライドは何回も renme 関数を呼ぶ場合には効果的だが、今回のように 1 回だけ呼ぶ場合にはメリットがあまり無い。

$ perl -le 'use File::Rename qw(rename); print rename("hoge.txt", "hage.txt");'

File::Rename::rename には一括変換の機能もあるので、もう少しブリコってみよう。例えば、カレントディレクトリの中に 100 個の .jpg がありこれらを .jpeg に変える場合、mv コマンドを 100 回たたく代わりに 1 行スクリプトで 1 回で終わらせる。

perl -e 'use File::Rename qw(rename); @F=<*>; rename(@F,sub{s/\.jpg$/.jpeg/},1);'

これでカレントディレクトリ中の .jpg でファイルネームが終わるファイル全てを .jpeg で終わるようにリネームできる。<*> はカレントディレクトリのファイル全て、rename 関数に与える引数を絞るにはここで grep{} するほうがいいと思う。@F は適当な名前の一時的配列だが、rename 関数の中の @F を <*> に変えるをエラーがでるので注意。rename 関数の第 3 引数は詳細な出力をさせるオプショナルなフラグ、1 だと詳細出力それ以外は出力なし。

[unlink] ファイルを一括削除

ファイルを選んで消そう、削除しようと思う。

$ rm hoge.txt
$ perl -le 'print unlink("hoge.txt");'

これで OK。前使ったダイヤモンド演算子を使ってワイルドカード指定してみる。

$ rm *.txt
$ perl -le 'print unlink(<*.txt>);'

やはり、シェル本来の機能を使ったほうがいい。もう少しトリッキーな例を挙げてみよう。

$ find . -regex '^[A-Z][0-9]{4}.*\.txt$' -print0 | xarg -0 rm
$ perl -le '@F=grep{m/^[A-Z][0-9]{4}.*\.txt$/}<*>; print unlink(@F);'

これでカレントディレクトリのファイルの内、1 文字目がアルファベットの大文字でその後ろの 4 文字が数字で最後が .txt でおわるファイルを削除する。これはシェルでは面倒だろうとおもったが、find であっさり解決。更新日時とかファイルタイプとかで篩い分けしてもシェルに軍配があるような気がする。

シンボリックリンク

まぁ Perl をつかって貼ることもないんだけれど。perl でも出来るよということで。

$ perl -e 'symlink("hoge","hage")'

n 進数

ここまでくるとほとんど 1 行で書くメリットなどほとんどないように思われる。でも、自分の思考をすぐに書け、これを確かめられるのはとてもよいことだとと思う。その点において、あえてファイルにしなくてもシェルでかき始められる 1 行スクリプトは結構役に立つと思う。まぁ能書きはおいといて 10 進数から 20 進数へ変換してみた。

$ perl -le 'print join(" ",&sin(130344445,20)); sub sin{my $s=shift @_; my $p=shift @_; my $i=0; do{push(@a, $s%$p); $s=int($s/$p); $i++;}while($s!=0); return reverse @a;}'
2 0 14 13 1 2 5
|      i              =      a_0 * n ^ 0     +     a_1 * n ^ 1     +     a_2 * n ^ 2     + ...
|     (i) / n ^ 0     =     (a_0 * n ^ 0     +     a_1 * n ^ 1     +     a_2 * n ^ 2     + ...) / n ^ 0
|     (i) / n ^ 0     =      a_0 * n ^ 0     +     a_1 * n ^ 1     +     a_2 * n ^ 2     + ...
| mod((i) / n ^ 0, n) =  mod(a_0 * n ^ 0     +     a_1 * n ^ 1     +     a_2 * n ^ 2     + ..., n)
| mod((i) / n ^ 0, n) =  mod(a_0 * n ^ 0, n) + mod(a_1 * n ^ 1, n) + mod(a_2 * n ^ 2, n) + ...
| mod((i) / n ^ 0, n) =      a_0             +     0               +     0               + ...
| mod((i) / n ^ 0, n) =      a_0
|      i - a_0 * n ^ 0              =     a_1 * n ^ 1     +     a_2 * n ^ 2     + ...
|     (i - a_0 * n ^ 0) / n ^ 1     =    (a_1 * n ^ 1     +     a_2 * n ^ 2     + ...) / n ^ 1
|     (i - a_0 * n ^ 0) / n ^ 1     =     a_1 * n ^ 0     +     a_2 * n ^ 1     + ...
| mod((i - a_0 * n ^ 0) / n ^ 1, n) = mod(a_1 * n ^ 0     +     a_2 * n ^ 1     + ..., n)
| mod((i - a_0 * n ^ 0) / n ^ 1, n) = mod(a_1 * n ^ 0, n) + mod(a_2 * n ^ 1, n) + ...
| mod((i - a_0 * n ^ 0) / n ^ 1, n) =     a_1             +     0               + ...
| mod((i - a_0 * n ^ 0) / n ^ 1, n) =     a_1
|      i - a_0 * n ^ 0 - a_1 * n ^ 1              =      a_2 * n ^ 2     + ...
|     (i - a_0 * n ^ 0 - a_1 * n ^ 1) / n ^ 2     =     (a_2 * n ^ 2     + ...) / n ^ 2
|     (i - a_0 * n ^ 0 - a_1 * n ^ 1) / n ^ 2     =     (a_2 * n ^ 0     + ...)
| mod((i - a_0 * n ^ 0 - a_1 * n ^ 1) / n ^ 2, n) = mod((a_2 * n ^ 0     + ...), n)
| mod((i - a_0 * n ^ 0 - a_1 * n ^ 1) / n ^ 2, n) = mod((a_2 * n ^ 0, n) + ...
| mod((i - a_0 * n ^ 0 - a_1 * n ^ 1) / n ^ 2, n) =      a_2             + ...
| mod((i - a_0 * n ^ 0 - a_1 * n ^ 1) / n ^ 2, n) =      a_2
| i = a_0 * n ^ 0 +  a_1 * n ^ 1 +  a_2 * n ^ 2  + ...
| i = a_0 * n ^ 0 + (a_1 * n ^ 0 +  a_2 * n ^ 1  + ...) * n ^ 1
| i = a_0 * n ^ 0 +  a_1 * n ^ 0 + (a_2 * n ^ 0  + ...) * n ^ 2
| i = a_0 * n ^ 0 +  a_1 * n ^ 0 +  a_2 * n ^ 0 (+ ...) * n ^ 3

コーヒーの色は水色

「1000cc バイクの色は青、ツツジの色は灰色、フリースの色は白、オフィスの色は空色」コーヒーの色は水色というネタを読んで、じゃぁ探してみようという気になった。HTML の色指定で使えるアルファベットは a-f の 6 種類、これを 6 つ並べるとこんな感じ。まぁ、どれが意味あるアルファベットかは人間様が判断するのだけれど。

$ perl -e '@F=qw(a b c d e f); for($i=0;$i%lt;6*6*6*6*6*6;$i++){@a=&sin($i,6); $#a=6; @a=reverse @a; foreach(@a){print "$F[$_]"}print"\n";} sub sin{my $s=shift @_; my $p=shift @_; my @a=(); my $i=0; do{push(@a, $s%$p); $s=int($s/$p); $i++;}while($s!=0); return @a;}'| head
aaaaaaa
aaaaaab
aaaaaac
aaaaaad
aaaaaae
aaaaaaf
aaaaaba
aaaaabb
aaaaabc
aaaaabd

で、まず考えてしまったのが、6 重の for ループ。これだと 1 行スクリプトとしては長すぎ。ということで、6 進数を考えて 6 の n 乗の係数をそれぞれの桁として使う。これで解決。

出力が多すぎなので考え方逆に。単語辞書から 6 文字の単語だけ抽出、意味は大文字が小文字になったところで変わらないので小文字に正規化。sort | uniq で重複削除

$ perl -lne 'while(m/(\w+)/g){print lc $1}' edict | perl -ne 'if(m/^\w{6}$/){print;}' | sort | uniq | less

16 進数で表せる 0 から f までの文字でそれ以外の文字と形が似ているものをピックアップすると下のようになる。

0 - O,D,o
1 - I,l
2 - z,Z
3,4,5
6 - b
7,8,9,a
b - 6
c,d,e,f
A,B,C
D - 0
E
c0ffee - coffee

右から左への変換を行い、変換後の文字列が [0-9a-fA-F]{6}となれば OK である。ただし、変換前は全部小文字になっているので、右は全部小文字にしておく。また、b を 6 にする変換は必要ない。変換しなくても 16 進で表せるから。同様に 6 を b にする変換と 0 を D にする変換も。したがって必要な変換はこんな感じ。

tr/odilz/00112/;

一気に全部やって篩にかけるにはこんな感じ。

$ perl -lne 'while(m/(\w+)/g){print lc $1}' edict | perl -ne 'if(m/^\w{6}$/){print;}' | sort | uniq | perl -ne 'chomp; $a=$_; tr/odilz/00112/; if(m/^[0-9A-Fa-f]{6}$/){print "$a\t$_\n";}' | less

で、篩にかけた結果、残ったのが下。初めのカラムが変換前で 2 番目のカラムが色コードである。3 カラム目には適当な意味を手作業で付けた。

1000cc  1000cc  排気量
289bce  289bce  ?
800000  800000  ?
abelia  abe11a  《植物》ツクバネウツギ属の低木  スイカズラ科の植物
ablaze  ab1a2e  輝いている、燃え立っている、興奮している
acacia  acac1a  《植物》アカシア
accede  acce0e  継ぐ、継承する、就く、就任する、権力の座に就く
acidic  ac101c  酸っぱい、酸の、酸性の
adelie  a0e11e  南極の地名
albedo  a1be00  アルベド◆太陽の光を地球が反射する割合。
alcedo  a1ce00  カワセミ
allele  a11e1e  対立遺伝子
allied  a111e0  同盟している
azalea  a2a1ea  ツツジ
babble  babb1e  せせらぎ(の音)
baddie  ba001e  悪役、悪者◆映画などに登場する
balboa  ba1b0a  バルボア◆パナマの貨幣単位
ballad  ba11a0  民間伝承の物語詩、バラード形式の詩・曲、歌謡、民謡
baobab  ba0bab  《植物》バオバブ
beefed  beefe0  強化?
befall  befa11  〔災難・異変などが〕起こる、生じる
belief  be11ef  信仰、信条
belize  be112e  ベリーズ(中米の国)
biblid  b1b110  ?
bifida  b1f10a  二叉
billed  b111e0  くちばしのある
biloba  b110ba  《植物》イチョウ◆学名
bladed  b1a0e0  ブレード[刀]の付いた
bobbed  b0bbe0  ショートカットの、断髪の
bodice  b001ce  女性用胴着
bodied  b001e0  ~な体を持つ
boiled  b011e0  ボイルした、ゆでた、煮た
cabala  caba1a  密教、神秘的教義
caddie  ca001e  《ゴルフ》キャディー、使い走りや雑用をする人
calico  ca11c0  キャラコ、キャリコ、更紗、サラサ
called  ca11e0  ~と呼ばれている
celiac  ce11ac  セリアック病患者
celica  ce11ca  神々しい、天上の
celled  ce11e0  ~細胞の[を持つ]
cicada  c1ca0a  《虫》セミ
cobble  c0bb1e  敷石、栗石、丸石、玉石
coffee  c0ffee  コーヒー
coiled  c011e0  ひもなどでグルグル巻きにされた
collie  c0111e  コリー◆スコットランド原産の牧羊犬
cooled  c001e0  冷却される
coolie  c0011e  クーリー、日雇い労働者◆インド、中国での
dabble  0abb1e  〔戯れ程度に〕水を跳ねかける
dacelo  0ace10  ワライカワセミ
daidai  0a10a1  ?
dazzle  0a221e  輝くもの、まぶしい光、まぶしさ、輝き
decade  0eca0e  10年間、10年
decide  0ec10e  決定する、決心する、決意する
decode  0ec00e  〔符号化された情報の〕復号、デコード
decola  0ec01a  ?
deface  0eface  ~の外観を損なう
defile  0ef11e  ~の美観を損なう、汚す、不潔にする
docile  00c11e  従順な、素直な、おとなしい、御しやすい
doodle  00001e  いたずら書き
ebcdic  ebc01c  拡張2進化10進コード(extended binary coded decimal interchange code)
edible  e01b1e  食料品、料理、食事
efface  efface  〔絵・文字・痕跡などを〕こすって消す、削除する
eiffel  e1ffe1  エッフェル
elodea  e100ea  カナダモ(植物)
fabled  fab1e0  寓話として名高い、伝説的な
facade  faca0e  〈フランス語〉表面、外観、外見、一面、うわべ、見せかけ
facial  fac1a1  美顔術
facile  fac11e  手軽な、容易な、たやすく得られる、軽快な、軽薄な、器用な
failed  fa11e0  失敗した、不成功に終わった
feeble  feeb1e  体力の弱った、弱々しい、か弱い、力がない、もろい
fiddle  f1001e  《楽器》フィドル、バイオリン
filial  f111a1  子供の、子供としてふさわしい
filled  f111e0  一杯詰まった、満杯の、充満した、詰め物をした
fizzle  f1221e  弱く消えてしまうようにシューと音を出す
fleece  f1eece  フリース◆ポリエステル起毛の合成繊維。
folded  f010e0  折られた、折り畳まれた
fooled  f001e0  だまされれる
icicle  1c1c1e  つらら、氷柱
iodide  10010e  ヨウ化物
labial  1ab1a1  唇音
lablab  1ab1ab  (植物)フジマメ
laddie  1a001e  若いの
leaded  1ea0e0  有鉛の
leafed  1eafe0  葉のある
liable  11ab1e  〔法的に〕責任がある、責任を負うべき、~を免れない
libido  11b100  《精神分析》リビドー、性的衝動、性欲
lidded  1100e0  〔容器などに〕ふたのある
loaded  10a0e0  荷物を積んだ
locale  10ca1e  現場、場所
office  0ff1ce  事務所
zodiac  2001ac  十二宮図、黄道帯、獣帯

意味が取れないものがいくつか混じっているが後は目で抜く。とりあえず下にまとめた。

意味の取れる色のリスト
1000cc1000cc応用が利きそう。
acaciaacac1a《植物》アカシア、色との対応が付いていたらすごい。
allelea11e1e対立遺伝子、lがそれぞれ1に対応している。でも、単語がなじみなさすぎ
azaleaa2a1eaツツジ、実物との対応
babblebabb1eせせらぎ(の音)、実物との対応
balboaba1b0aバルボア◆パナマの貨幣単位、2箇所変化
baobabba0bab《植物》バオバブ、1箇所変化
befallbefa11〔災難・異変などが〕起こる、生じる、2箇所変化
cabalacaba1a密教、神秘的教義、1箇所変化
cobblec0bb1e敷石、栗石、丸石、玉石、2箇所変化
coffeec0ffeeコーヒー、1箇所変化
doodle00001eいたずら書き、ほとんど原形とどめず
effaceefface〔絵・文字・痕跡などを〕こすって消す、削除する、一切変化なしの完全形
feeblefeeb1e体力の弱った、弱々しい、か弱い、力がない、もろい、1箇所変化
fleecef1eeceフリース◆ポリエステル起毛の合成繊維。、1箇所変化
locale10ca1e現場、場所、3箇所変化
office0ff1ce事務所、2箇所変化
zodiac2001ac十二宮図、黄道帯、獣帯、ほとんど原形とどめず。

使えそうなのは 1000cc、azalea、cabala、coffee、fleece、office ぐらいか。ということで最初の話に戻る。

MD5 とか SHA1 でハッシュ計算

これもあえて Perl でしなくてもいいことなのだけど、ごくたまに MD5 とか SHA1 のハッシュを求めんといかん状況に追い込まれることがある。ごくたまにしかない状況のためにいちいち探してインストールしてというのはあまり好きじゃない。ということで、あるメッセージのハッシュ値を計算させるには下のようにする。

$ perl -le 'use Digest::MD5 qw(md5_hex); print md5_hex("admin");'
21232f297a57a5a743894a0e4a801fc3
$ perl -le 'use Digest::SHA1 qw(sha1_hex); print sha1_hex("admin");'
d033e22ae348aeb5660fc2140aec35850c4da997

MD5 のモジュールはすでにインストール済みだったが、SHA1 はインストールされてなかったので導入した。

1 行で CPAN モジュールの導入

うちのサーバなので、勝手にモジュールのインストールが可能だ。このような場合は何をするにも楽なのだ。root になって Diget::SHA1 を導入してみる。

# cpan -i Digest::SHA1

1行とかこだわらなければ

# cpan
cpan> install Digest::SHA1
cpan> quit

あえてタイプ数の多い方法を使いたければ

# perl -MCPAN -e shell

こんな感じで OK。perl ってのはすごいね。きっと Windows だろうが Linux だろうが、perl の世界にはいってしまえば OS 間の差異は取り払われてしまうのかもしれない。まぁそれはそれとて、導入できたか確認とバージョンの確認をしてみよう。下の 2 つは同じこと、失敗しているとここでエラーメッセージが出る。

$ perl -MDigest::SHA1 -le 'print $Digest::SHA1::VERSION;'
$ perl -MDigest::SHA1 -le 'print Digest::SHA1->VERSION;'

よくあるバージョン確認の方法は上の 2 つ。つまり、モジュールを use してモジュール内の $VERSION 変数を参照する方法。バージョンチェックの 3 番目は下のような感じ。長くなった割にあまり面白くないが、モジュールを読み込まないので多少メモリの節約になるかもしれない。

$ perl -MExtUtils::MakeMaker -le 'print MM->parse_version(shift)' /usr/lib/perl5/site_perl/5.8.6/i386-linux-thread-multi/Digest/SHA1.pm

たった 1 つのモジュールのバージョンをチェックするだけなら MakeMaker をつかうのはもったいない。モジュールのリストとバージョンをチェックするには下のようにするとよさげ。MakeMaker を使う方法では、実際にモジュールを読み込んでいるわけではないので (use とか require で) コンパイルが通るかどうかまではわからない。

$ perl -MExtUtils::MakeMaker -MFile::Find -le 'find(sub{$a=$File::Find::name; if(m/pm$/){print MM->parse_version($a)."\t".$a}},@INC);' | less

当然といえば其の通りだが勝手にどんどんインストールしてしまうとトラブルが起きる。うちの etch では「apt-get update 後 (perl のアップグレード含む) の perl プログラムが軒並み下のエラーメッセージを出して終了する」というトラブルが起きた。

# apt-get update
# apt-get upgrade
# cpan(例えば。)
Errno architecture (i486-linux-gnu-thread-multi-2.6.18-6-k7) does not match executable architecture (i486-linux-gnu-thread-multi-2.6.18-6-686) at /usr/local/share/perl/5.8.8/Errno.pm line 11.
Compilation failed in require at /usr/local/share/perl/5.8.8/CPAN.pm line 1107.
BEGIN failed--compilation aborted at /usr/local/share/perl/5.8.8/CPAN.pm line 1107.
Compilation failed in require at /usr/local/bin/cpan line 175.
BEGIN failed--compilation aborted at /usr/local/bin/cpan line 175.
Can't call method "has_usable" on an undefined value at /usr/local/share/perl/5.8.8/CPAN/HandleConfig.pm line 502.
END failed--call queue aborted at /usr/local/bin/cpan line 175.

たしか、apt-get upgrade のときに perl を upgrade していたような気がする。んで、upgrade の前には cpan コマンドでいくつかのモジュールを install & update していた。Errno.pm でまずだめになっているようなのでどこにあるか探すと 2 つ出てくる。

$ locate Errno.pm
/usr/lib/perl/5.8.8/Errno.pm
/usr/local/share/perl/5.8.8/Errno.pm

Errno architecture とか言ってくるのはどっちだと言うことで grep

# grep "Errno architecture" /usr/lib/perl/5.8.8/Errno.pm /usr/local/share/perl/5.8.8/Errno.pm
/usr/local/share/perl/5.8.8/Errno.pm:   die "Errno architecture (i486-linux-gnu-thread-multi-2.6.18-6-k7) does not match executable architecture ($Config{'archname'}-$Config{'osvers'})";

ということで/usr/local/share/perl/5.8.8/Errno.pm を読みに言っている様子。じゃぁ @INC の順番を調べる。なぜかワンライナーはいけるようで下のような感じ。

# perl -e 'print "@INC"'
/etc/perl /usr/local/lib/perl/5.8.8 /usr/local/share/perl/5.8.8 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.8 /usr/share/perl/5.8 /usr/local/lib/site_perl .

なるほど。やはりこの順番でよみにいってるのだな。2 つの Errno.pm のバージョンチェック

# grep "VERSION" /usr/lib/perl/5.8.8/Errno.pm /usr/local/share/perl/5.8.8/Errno.pm
/usr/lib/perl/5.8.8/Errno.pm:our (@EXPORT_OK,%EXPORT_TAGS,@ISA,$VERSION,%errno,$AUTOLOAD);
/usr/lib/perl/5.8.8/Errno.pm:$VERSION = "1.09_01";
/usr/lib/perl/5.8.8/Errno.pm:$VERSION = eval $VERSION;
/usr/local/share/perl/5.8.8/Errno.pm:our (@EXPORT_OK,%EXPORT_TAGS,@ISA,$VERSION,%errno,$AUTOLOAD);
/usr/local/share/perl/5.8.8/Errno.pm:$VERSION = "1.10";
/usr/local/share/perl/5.8.8/Errno.pm:$VERSION = eval $VERSION;

つまり、version 1.10 のほうを読みに行っているのね。それはおそらく cpan コマンドで更新されたものだな。ということでこちらを Errno.pm から Errno.pm.org にリネーム

# mv /usr/local/share/perl/5.8.8/Errno.pm{,.org} -i

で、cpan を走らせてみる。

# cpan
cpan shell -- CPAN exploration and modules installation (v1.9301)
ReadLine support enabled
cpan[1]>

走るな。じゃぁいままでだめだったプログラムを走らせて見る。

$ perl ./hoge.pl

走ったか。全く良くわからんがリネームでこのトラブルは回避できた予感。今回のトラブルは cpan がインストールするディレクトリと apt-get がインストールモジュールのディレクトリが違うことで起こってしまったと思われる。同じにしたらしたで問題ありなのでとりあえずこの状態で様子見運用します。

cpan のダウンロード先変更

まずは今の設定をチェックする。

# cpan
cpan> o conf urllist
        ftp://ftp.ring.gr.jp/pub/lang/perl/CPAN/
cpan> o conf urllist pop ftp://ftp.ring.gr.jp/pub/lang/perl/CPAN/
cpan> o conf urllist push http://ftp.riken.jp/lang/CPAN/
cpan> o conf urllist
        http://ftp.riken.jp/lang/CPAN/
cpan> o conf commit
cpan> q

変更が出来たらその設定済みの内容を Config.pm に反映する。再度 cpan を起動して設定内容が破棄されていないかどうかを確認。

# cpan
cpan> o conf urllist
        http://ftp.riken.jp/lang/CPAN/
  1. cpan urllist - Google 検索
  2. CPAN の ダウンロード先(URL リスト)を変更する方法 :: Drk7jp
  3. CPAN/SITES

html エスケープ

html を直に書くとき、> を &gt; に書き換えたりする。これを HTML エスケープというのかは知らないが、タグにはさまれた内容に含めていけないものを含めてもよい形に変換することをぼくは HTML エスケープと呼んでいる。これを 1 行でしてみよう。

$ perl -pe 'BEGIN{use HTML::Entities qw(encode_entities);} encode_entities($_,"<>&");' hoge.txt
$ perl -MHTML::Entities -pe 'encode_entities($_,"<>&");' hoge.tex

こんな感じで標準出力にエスケープされた内容が表示される。BEGIN{} ブロックで HTML::Entities をロードして、この中でユーザ空間の encode_entities を HTML::Entities::encode_entities に差し替える。-p オプションで hoge.txt の内容をいちいち出力、出力前にエスケープ処理を行う。モジュールをよく読めばその下のようにもかける。このように出力されたものを pre の中に含め、ブラウザで表示させると、エスケープ前の文字列が見える。

CGI.pm を使うという手もある。CGI.pm は確か何もしなくても導入してあったと思う。色々入れすぎて訳わかんなくなってるけど。例えば下のような感じ、こちらのほうが短くてすむので少しうれしゅうございますだ。でも、これだとうまくいかない場合がある。やはり正攻法は encode_enities だろう。

$ perl -MCGI -pe '$_=CGI::escapeHTML($_);' hoge.tex

URL エンコードと URL デコード

hoge.txt はテキストファイル。日本語とか英語とか空行とか改行とか <>&" とかごちゃ混ぜに入っている。

$ cat hoge.txt
日本語
eigo
<
>
&
"
$ perl -MCGI::Util -pe '$_=CGI::Util::escape($_);' < hoge.txt
%E6%97%A5%E6%9C%AC%E8%AA%9E%0Aeigo%0A%0A%3C%0A%3E%0A%26%0A%22%0A%0A%0A

変換された内容を見てみるとなんとなく正しく変換されているようだ。元に戻すには下のようにする。

$ perl -MCGI::Util -pe '$_=CGI::Util::escape($_);' < hoge.txt > hoge_escape.txt
$ perl -MCGI::Util -pe '$_=CGI::Util::unescape($_);' < hoge_escape.txt
日本語
eigo
<
>
&
"

ところでこういう処理のこと URL エンコード、URL デコードっていうのは正しいのかな。それとも URL エスケープ、URL アンエスケープって言うのが正しいのかな。

  1. 知らないことがあってもへっちゃらさ: URL エンコードされた文字を Perl でデコードしてみる
  2. JavaScriptでエンコードした値をPerlでデコード - Vox

バブルソート

暇ではないが、暇と認めたくはないが、本当は全く持って暇でもなんでもないのでけれど、暇だと思いたい。小人閑居して不全をなす。バブルソートをあえて 1 行で書く必要など全くないのだけれど、これも腐りきった脳髄の刺激のためにやっておく必要があるだろう。

$ perl -le '@F=qw(8 4 3 7 6 5 2 1); for($i=0;$i<@F;$i++){for($j=1;$j<@F-$i;$j++){print "@F | $i $j"; if($F[$j]<$F[$j-1]){@F[$j-1,$j]=@F[$j,$j-1];}}} print "@F";'
8 4 3 7 6 5 2 1 | 0 1
4 8 3 7 6 5 2 1 | 0 2
4 3 8 7 6 5 2 1 | 0 3
4 3 7 8 6 5 2 1 | 0 4
4 3 7 6 8 5 2 1 | 0 5
4 3 7 6 5 8 2 1 | 0 6
4 3 7 6 5 2 8 1 | 0 7
4 3 7 6 5 2 1 8 | 1 1
3 4 7 6 5 2 1 8 | 1 2
3 4 7 6 5 2 1 8 | 1 3
3 4 6 7 5 2 1 8 | 1 4
3 4 6 5 7 2 1 8 | 1 5
3 4 6 5 2 7 1 8 | 1 6
3 4 6 5 2 1 7 8 | 2 1
3 4 6 5 2 1 7 8 | 2 2
3 4 6 5 2 1 7 8 | 2 3
3 4 5 6 2 1 7 8 | 2 4
3 4 5 2 6 1 7 8 | 2 5
3 4 5 2 1 6 7 8 | 3 1
3 4 5 2 1 6 7 8 | 3 2
3 4 5 2 1 6 7 8 | 3 3
3 4 2 5 1 6 7 8 | 3 4
3 4 2 1 5 6 7 8 | 4 1
3 4 2 1 5 6 7 8 | 4 2
3 2 4 1 5 6 7 8 | 4 3
3 2 1 4 5 6 7 8 | 5 1
2 3 1 4 5 6 7 8 | 5 2
2 1 3 4 5 6 7 8 | 6 1
1 2 3 4 5 6 7 8

できて当たり前だぎゃね。まぁ今回は配列要素の入れ替えの勉強になったからよしとしよう。C 言語だとこんな感じの書き語って許されてたんだっけかなぁ。たしか一時変数に保存して上書きするという方法でやっていたような気がする。

stat で ls の代わり

やってみそ

$ perl -le 'foreach(<*>){print join("\t",stat(),$_);}'

やってみた、これ以上書くとなんだかせっかくの 1 行のメリットが失われる感があるのでやめておこう。それぞれのカラムが何を意味しているのかは調べてほしい。stat 系のモジュールは結構たくさんある。出力を ls っぽくしてくれるものとか。やっぱり考えることはみな同じだなぁ。

ディレクトリを作れ

カレントに test というディレクトリをつくるにはこんな感じ。でも、コマンドで mkdir -p のようにしてやるような多重階層のディレクトリ作成は無理っぽい。どっかのモジュールにあったような気がするんだけれど思い出せん。

$ perl -e 'mkdir("test");'

1 行で平均値

それぞれのコラムに対応した平均値を求めてみる。入力ファイルは 5 コラムで 4 行のタブ区切りテキストで下のような感じ。

$ cat hoge.dat
1       2       3       4       5
6       7       8       9       10
11      12      13      14      15
16      17      18      19      20

で、下のようにして平均値を出力した。重要なのは、hoge.dat の最後に改行がないということ。最後に改行があると、最後に $i が 5 となるため、正確でない値がでる。

$ perl -alne 'map{$S[$_]+=@F[$_]}(0..$#F);$i++; END{print join"\t",map{$_/$i}@S;}' hoge.dat
8.5     9.5     10.5    11.5    12.5

特定の行のみ出力 (head の代替)

head の代替をするなら下のような感じ。

$ head -n 100 hoge.dat
$ perl -ne 'print if 1 .. 100' hoge.dat

行数を知っていれば tail の代替もできる。行数が 200 行として最後から 50 行を出力するならば下のような感じ。

$ tail -n 50 hoge.dat
$ perl -ne 'print if 251 .. 200' hoge.dat

gnuplot と組み合わせるとかなり便利な感じ。

文字列の検索 (grep の代替)

hoge が含まれる行を表示する。-p オプションを使わないのがミソ。-p オプションは常に出力が必要な場合に付ける。1 行 if 文の書き方がわかれば 2 行目でも同じこと。

$ perl -ne 'if(m/hoge/){print;}' file
$ perl -ne 'print if /hoge/' file

大文字小文字の区別を無視するなら、m//i で下のようにする。これだと Hoge も HOGE も hOgE にも引っかかる。

$ perl -ne 'if(m/hoge/i){print;}' file
$ perl -ne 'print if /hoge/i' file

perl で google PageRank を取得する

あるサイトの PageRank を知りたいと思ったときに GoogleToolbar を入れなくてもわかる方法がある。其の方法とは google toolbar が叩いている URL をブラウザなりで叩くことだが、この場合には pagerank を調べてたいサイトに対応したチェックサム (ch=) の計算が必要になる。この計算と受信した内容をパースしてくれるモジュールが WWW::Google::PageRank。

$ perl -MWWW::Google::PageRank -e 'print scalar WWW::Google::PageRank->new->get("http://www.google.com/");'
  1. page rank チェック toolbar - Google 検索
  2. ページランクとGoogle toolbarについて by Eva
  3. features=Rank - Google 検索
  4. WWW::Google::PageRank - Perlメモ - perlmemoグループ
  5. はっぴぃ・りなっくす - Google PageRank - Tools > Google - SmartSection
  6. Geekなぺーじ : Googleページランクの取得(WWW::Google::PageRank)
  7. Perl Tips | Perl で、Google の PageRank を表示する方法
  8. oogle PageRank perl - Google 検索
  9. WWW::Google::PageRank - Query google pagerank of page - search.cpan.org
  10. MobileRead Forums - View Single Post - Google PageRank Checksum Algorithm
  11. Google PageRank Checksum Algorithm - MobileRead Forums

UNIX time が「1234567890」になる時間

$ perl -le 'print scalar localtime 1234567890'
Sat Feb 14 08:31:30 2009

1行スクリプトをスクリプトファイルにする

1行スクリプトと言っても、何回も使うならいちいち入力するのは面倒なので同等の機能を持ったスクリプトファイルにしましょう。説明は日本語 perl texinfo - Optionに詳しい。とにかく、1行目にperlのパス-Mと-e以外のオプションがある場合はそれを続け、-Mがある場合は2行目にuseを置いて其の後に-M以降、3行目に-e ''で括った中身を書けばOKのような感じ。ためしに以前書いたhtmlエスケープの1行スクリプトをスクリプトファイルにしてみる。ターゲットは下のような感じ。

$ perl -MHTML::Entities -pe 'encode_entities($_,"<>&");' hoge.tex

これは下のように書き下せる。

$ cat htmlescape.pl
#!/usr/bin/perl -p
use HTML::Entities;
encode_entities($_,"<>&");
$ chmod +x htmlescape.pl
$ mv htmlescape.pl ~/bin/

これに名前htmlescape.plを付けて、実行属性を加えて、パスの通った場所においておけばいつでも使える。使い方は下のような感じ。

$ ./httpescape.pl hoge.tex

せっかく1行でかけるのだからbashのaliasにしてしまうのが一番楽なのかも知れん。そういえば、#!から始まる行のことをシェバング行と呼ぶとか呼ばないとか。で、#シャープと!バングでシェバングだとか。

-pオプション

-pオプションをつかって下のように書いた場合。

$ perl -pe 's/a/b/g' file

これを書き下すと下のようになるそうだ。

while(<>){
        s/a/b/g; # '...' の中身
        print;
}

まぁ多少わかりにくいかも知れんので、正確さは失われるがもっと書き下してみた。

open IN, "file"; # 引数
while(<IN>){
        $_ =~ s/a/b/g; # '...' の中身
        print $_;
}
close IN;
exit;

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2003-11-13T19:28:59+09:00
  2. Modified: 2003-11-13T13:55:44+09:00
  3. Generated: 2017-07-04T23:09:27+09:00