Perl で大きな数字を扱うと、計算結果があわないという相談を受けた。
そもそもすごい桁数。3000億 * 4000億って、これ何の計算なの? と聞くと、金額計算のプログラムで、巨大数値入力テストでのエラーケースだという。なるほど、納得。
POSIX って、POSIX 規格のモジュール? となると、これって POSIX の仕様?
相談してきた人は管理者ではないので、ディストリビューションは不明。TurboLinux なのかな? それにしてもなかなかに古いなあ。まあ、特定環境で使うものだし、仕方がないでしょう。
Perl も古い。5.005_03 じゃなくて 5.005_02 か。おや? Hirofumi Watanabe って Ruby の ML などでみかける わたなべ ひろふみ さん?
あー、確かに 232898639000 よりも少なくなるね。
floor() を使わなければ OK と。
もちろん、標準の名前空間に floor() が無いことも確認。
調べてみると、POSIX::floor は C のライブラリを呼んでるだけということがわかった。POSIX 規格の関数を実装したライブラリだというなら、そうだろうなあ。結局そこの仕様を調べないと原因も対処も確定しない。
相談してきた人によると、どうするかは今後検討するとのこと。相談者はもともと別件でこのプログラムを修正していた。テストケースを増やしたところ、このエラーを見つけることができたとのこと。テスト重要。
1. 今回の計算は「丸め誤差」ではなく、オーバーフローが原因。
2. 今回の計算は、IEEE754 に則った倍精度浮動小数点数で表現できる範囲を
超えている。
3. Perl は倍精度浮動小数点数で計算している。
4. Perl であれば Math::BigFloat を使うとより高い精度で計算ができる。
この分野って過去に情報処理技術者試験のために学んだくらいで、普段全く使わない。1の補数とか2の補数とか、少ないビットで大きい数を表すとかね。
追記をしていても、自分の中でも消化し切れてない感じがある。「オーバーフローしている」のに、なぜ「正しい計算結果が出ている (ように見える)」のかがよくわからない。たまたまそういう結果になるような数値を使って計算したから? 「下二桁が00だから、その部分を除いて考えると倍精度浮動小数点数の表現範囲に収まる」と思いかけたけど、明らかに足りないし。少しずつ勉強していこう。
http://www.kt.rim.or.jp/~kbk/zakkicho/zakkicho11.html#D20060 ...
メールで頂いた解説と上記 URL の説明を読み、実際に自分で試してみてやっと理解することができた。
オーバーフローの結果失われた数字が小さかったことと、四捨五入がオーバーフローして失われた分を偶然回復するような動きとなったために、結果として正しい計算ができたように見えたということだ。
- Perl の丸め誤差?
349347958500 * 466593284000 / 699889926000 という計算が 232898639000 にならずに 232898638999 になるという。そもそもすごい桁数。3000億 * 4000億って、これ何の計算なの? と聞くと、金額計算のプログラムで、巨大数値入力テストでのエラーケースだという。なるほど、納得。
- ソースコードを確認
ちゃんとソースを持ってきてくれていたので、該当箇所を探してみる。あった。って、floor って関数を呼んでるのね。Perl というかこれが悪いんじゃないの? これってどの名前空間のメソッド? と思って先頭行の use を探してみるとそれらしきものがあった。use POSIX qw(math_h)
POSIX って、POSIX 規格のモジュール? となると、これって POSIX の仕様?
- 実行環境を確認
テスト環境のターミナルを使わせてくれたので環境を確認。Welcome to Linux 2.2.16.
valeria:~> uname -a
Linux valeria 2.2.16 #2 Mon Aug 14 23:19:26 JST 2000 i686 unknown
相談してきた人は管理者ではないので、ディストリビューションは不明。TurboLinux なのかな? それにしてもなかなかに古いなあ。まあ、特定環境で使うものだし、仕方がないでしょう。
valeria:~> perl -v
This is perl, version 5.005_02 built for i486-linux
Copyright 1987-1998, Larry Wall
Japanization patch 4 by Yasushi Saito, 1996
Modified by Hirofumi Watanabe, 1996-1998
jperl5.005_02-981225
EUC version
Perl も古い。5.005_03 じゃなくて 5.005_02 か。おや? Hirofumi Watanabe って Ruby の ML などでみかける わたなべ ひろふみ さん?
- コマンドラインから試す
Perl って比較的簡単にコマンドラインから試せるから好き。valeria:~> perl -MPOSIX -le 'print floor(349347958500 * 466593284000 / 699889926000)'
232898638999
あー、確かに 232898639000 よりも少なくなるね。
valeria:~> perl -MPOSIX -le 'print 349347958500 * 466593284000 / 699889926000'
232898639000
floor() を使わなければ OK と。
valeria:~> perl -le 'print floor(349347958500 * 466593284000 / 699889926000)'
Undefined subroutine &main::floor called at -e line 1.
もちろん、標準の名前空間に floor() が無いことも確認。
調べてみると、POSIX::floor は C のライブラリを呼んでるだけということがわかった。POSIX 規格の関数を実装したライブラリだというなら、そうだろうなあ。結局そこの仕様を調べないと原因も対処も確定しない。
相談してきた人によると、どうするかは今後検討するとのこと。相談者はもともと別件でこのプログラムを修正していた。テストケースを増やしたところ、このエラーを見つけることができたとのこと。テスト重要。
- 追記
上記のメモをご覧になった方から、解説と Perl および C のサンプルコードをメールで頂いた。ありがとうございます。その結果理解したことをメモ。1. 今回の計算は「丸め誤差」ではなく、オーバーフローが原因。
2. 今回の計算は、IEEE754 に則った倍精度浮動小数点数で表現できる範囲を
超えている。
3. Perl は倍精度浮動小数点数で計算している。
4. Perl であれば Math::BigFloat を使うとより高い精度で計算ができる。
この分野って過去に情報処理技術者試験のために学んだくらいで、普段全く使わない。1の補数とか2の補数とか、少ないビットで大きい数を表すとかね。
追記をしていても、自分の中でも消化し切れてない感じがある。「オーバーフローしている」のに、なぜ「正しい計算結果が出ている (ように見える)」のかがよくわからない。たまたまそういう結果になるような数値を使って計算したから? 「下二桁が00だから、その部分を除いて考えると倍精度浮動小数点数の表現範囲に収まる」と思いかけたけど、明らかに足りないし。少しずつ勉強していこう。
- 追記2
以下でも解説を頂いた。ありがとうございます。http://www.kt.rim.or.jp/~kbk/zakkicho/zakkicho11.html#D20060 ...
これにしたがってprintの整形ルーチンが四捨五入して数値を丸めているので一見合っているかのように見えるだけ。それに対してfloorで丸めると切り捨てになるので、桁あふれが起きて生じた小数部分の分、答えが違うということになる。
メールで頂いた解説と上記 URL の説明を読み、実際に自分で試してみてやっと理解することができた。
オーバーフローの結果失われた数字が小さかったことと、四捨五入がオーバーフローして失われた分を偶然回復するような動きとなったために、結果として正しい計算ができたように見えたということだ。
Perl5 から見た Perl6 の変更点。最近では私は Perl を使うのは趣味のプログラミングがメインになっちゃったけど、動向くらいは追っておきたいのでメモ。
既存の文法がかなり変わってる。
文字列連結がは . (ドット) じゃなくて ~ (チルダ)。Perl は動的に方が決まるので、変数を数値として評価するか文字列として評価するかを演算子で明示しなきゃならないからね。
メソッド呼び出しが -> から . になった。これのために文字列演算子が . から ~ になったようだ。
新機能や新構文がたくさん追加。正規表現は「ルール」と呼ばれることになった。もちろん、旧来の Perl5 の正規表現も使える。書籍の「詳説 正規表現」の次版が出るとしたら、書名は「詳説 正規表現 + ルール」になるんだろうか?
以下のサイトを見てみたが、ルールは非常に高機能になっていろいろできそうな雰囲気。
Perl6 Rules(新たな正規表現)
http://www9.ocn.ne.jp/~ymt/perl6/rules.html
Perl6 は Parrot という環境で実行される。Parrot は Java や .NET の VM (Virtual Machine) のようなもの。これはかなり前から言われてたなあ。
via: Software Design 2006年3月号 第1特集
既存の文法がかなり変わってる。
文字列連結がは . (ドット) じゃなくて ~ (チルダ)。Perl は動的に方が決まるので、変数を数値として評価するか文字列として評価するかを演算子で明示しなきゃならないからね。
メソッド呼び出しが -> から . になった。これのために文字列演算子が . から ~ になったようだ。
新機能や新構文がたくさん追加。正規表現は「ルール」と呼ばれることになった。もちろん、旧来の Perl5 の正規表現も使える。書籍の「詳説 正規表現」の次版が出るとしたら、書名は「詳説 正規表現 + ルール」になるんだろうか?
以下のサイトを見てみたが、ルールは非常に高機能になっていろいろできそうな雰囲気。
Perl6 Rules(新たな正規表現)
http://www9.ocn.ne.jp/~ymt/perl6/rules.html
Perl6 は Parrot という環境で実行される。Parrot は Java や .NET の VM (Virtual Machine) のようなもの。これはかなり前から言われてたなあ。
via: Software Design 2006年3月号 第1特集
C# の正規表現クラスに複数のオプションを指定するには、 | で RegexOptions 列挙体の OR を取る。|| ではない。
んん? なんでエラーになってるの? .NET というか C# で複数の正規表現オプションを Regex クラスのコンストラクタに渡したいときは、OR を取れば良いんでしょう? かなり前にやった覚えがあるんだけど・・・。
あ、わかった。ビット演算の OR (|) じゃなくて論理演算の OR (||) を使ってる。あはは。これが原因か。以下のように修正して無事コンパイルできた。
RegexOptions 列挙体 - System.Text.RegularExpressions
http://www.microsoft.com/japan/msdn/library/default.asp?url= ...
- 今日の失敗 論理演算子 || を RegexOptions に使おうとした
文字列を強調処理する正規表現を書いていたところ、Regex クラスのコンストラクタに正規表現オプションとして RegexOptions 列挙体を渡すところでコンパイルエラーが出た。Regex highLight = new Regex(Regex.Escape(keyword), RegexOptions.IgnoreCase || RegexOptions.Multiline);
C:\CSProjects\Prototype\ConsoleApplication1\ConsoleApplication1\Class1.cs(30): 演算子 '||' を 'System.Text.RegularExpressions.RegexOptions' と 'System.Text.RegularExpressions.RegexOptions' 型のオペランドに適用することはできません。
んん? なんでエラーになってるの? .NET というか C# で複数の正規表現オプションを Regex クラスのコンストラクタに渡したいときは、OR を取れば良いんでしょう? かなり前にやった覚えがあるんだけど・・・。
あ、わかった。ビット演算の OR (|) じゃなくて論理演算の OR (||) を使ってる。あはは。これが原因か。以下のように修正して無事コンパイルできた。
Regex highLight = new Regex(Regex.Escape(keyword), RegexOptions.IgnoreCase | RegexOptions.Multiline);
- RegexOptions 列挙体でよく使うもの
ちなみに System.Text.RegularExpressions.RegexOptions 列挙体でよく使うのは以下の3つかな。Perl でも i m s などとして指定してたよね。最初は m と s の違いを実感できなかった。とくに、同時に指定したらどうなるのかがわかりにくかったので、2003-03-25 の「Perl の正規表現のオプション m と s」では 実際にどう使うかをまとめた。RegexOptions 列挙体 - System.Text.RegularExpressions
http://www.microsoft.com/japan/msdn/library/default.asp?url= ...
IgnoreCase
検索時に大文字と小文字を区別しないことを指定します。
Multiline
複数行モードを指定します。^ と $ の意味を変更して、文字列全体の先頭と末尾だけでなく、任意の行の先頭と末尾にもそれぞれが一致するようにします。
Singleline
単一行モードを指定します。\n 以外の任意の文字ではなく、すべての文字と一致するようにピリオド (.) の意味を変更します。
* ChangeLog メモに適切な改行を与える Perl One Liner
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [Perl] [ChangeLog メモ]
過去の ChangeLog メモを読み返していたら、ChangeLog のフォーマットに則っていないメモがたくさん見つかった。日付とアイテムの間に改行が入っていないというもの。
以前の私が書いていた、改行が足りず正しくない ChangeLog。
正しい ChangeLog。
2003年頃の私は Emacs 系のエディタを使わずに ChangeLog メモを書いていたため、書式の統一が取れていなかった。このまま放置するのは精神衛生上良くないので修正しておく。
-i.bak は元のファイルは ファイル名の末尾に .bak を付けてバックアップするというオプション。
-0777 はファイルを一気に読み込んで Perl ワンライナーに渡すというオプション。
-p は実行結果を出力するオプション。
-e は オプションの引数に与えた Perl スクリプトを実行するオプション。
念のためバックアップと置換後のファイルの diff を取ってざっと確認。うん、問題なさそう。
以前の私が書いていた、改行が足りず正しくない ChangeLog。
2003-06-16 Saito Hiroaki <hiroaki@example.jp>
* MS SQL Server: 文字列中のシングルクオートのエスケープ
SQL Server で使う Transact-SQL における文字列のエスケープ。
正しい ChangeLog。
2003-06-16 Saito Hiroaki <hiroaki@example.jp>
* MS SQL Server: 文字列中のシングルクオートのエスケープ
SQL Server で使う Transact-SQL における文字列のエスケープ。
2003年頃の私は Emacs 系のエディタを使わずに ChangeLog メモを書いていたため、書式の統一が取れていなかった。このまま放置するのは精神衛生上良くないので修正しておく。
- Perl One Liner で一括置換
こういうのは正規表現で一括置換するのが手軽で良い。使い捨てのワンライナー (1行スクリプト) を書いた。$ perl -i.bak -0777 -pe 's/^(\d\d\d\d-\d\d-\d\d\s+Saito Hiroaki\s+<.+?>\n)\t/$1\n\t/mg' log.txt
-i.bak は元のファイルは ファイル名の末尾に .bak を付けてバックアップするというオプション。
-0777 はファイルを一気に読み込んで Perl ワンライナーに渡すというオプション。
-p は実行結果を出力するオプション。
-e は オプションの引数に与えた Perl スクリプトを実行するオプション。
念のためバックアップと置換後のファイルの diff を取ってざっと確認。うん、問題なさそう。
- 範囲演算子
2005-03-29 の「RFC2045 Base64 で使用する文字の種類」で書いた Base64 で使用する文字列は以下の perl ワンライナーで生成した。$ perl -e 'for (a..z) { print $_ }'
abcdefghijklmnopqrstuvwxyz
$ perl -e 'for (A..Z) { print $_ }'
ABCDEFGHIJKLMNOPQRSTUVWXYZ
$ perl -e 'for (0..9) { print $_ }'
0123456789
範囲演算子 .. を使っている。a..z で a から z までを指定できる。数字も指定可能。
- ワンライナーなのに3つ書くのはなんか変
問題なのは、ワンライナーなのに三回記述していること。一行で望む出力をすべて得られるようにしたい。はじめは以下のようにすれば大文字と小文字両方のアルファベットが得られるかなと思ったんだけど、小文字しか得られなかった。$ perl -e 'for (a..Z) { print $_ }'
abcdefghijklmnopqrstuvwxyz
範囲の指定ルールがよくわからないが、範囲演算子 .. では大文字小文字を一気に指定できないのかもしれない。大文字小文字を一気に指定できたとしても、数字が残っている。どうしようかなー。
ちょっと考えて、以下のようにしてみたらうまくいった。カンマで区切って3つリストを指定するだけ。なんで最初にこれを思いつかなかったんだろう。別の視点から考えると物事を簡単に解決できることがある、という例だ。
$ perl -e 'for (a..z, A..Z, 0..9) { print $_ }'
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
perl 実行時に -I オプションでディレクトリを指定すると、ライブラリ検索パス @INC に追加できる。perl のヘルプを表示させたらちゃんと書いてあった。
今までは 2003-04-02 の「自作の Perl モジュールのインストール先」で書いた use lib '/home/hiroaki/script'; などとしていたけど、これを使わなくてもコマンドラインから指定できる。
こんな感じで、一般ユーザレベルでインストールしたモジュールのパスを指定する。
今までは 2003-04-02 の「自作の Perl モジュールのインストール先」で書いた use lib '/home/hiroaki/script'; などとしていたけど、これを使わなくてもコマンドラインから指定できる。
こんな感じで、一般ユーザレベルでインストールしたモジュールのパスを指定する。
perl -I ~/perl/lib
- ヘルプの表現が微妙に変わってる
5.00503 だと「一回以上使われるかも」という表現。5.8.6 だと 「複数の -I も許される」という表現。$ /usr/local/perl/bin/perl5.8.6 -help
Usage: /usr/local/perl/bin/perl5.8.6 [switches] [--] [programfile] [arguments]
(略)
-Idirectory specify @INC/#include directory (several -I's allowed)
$ /usr/bin/perl5.00503 -h
Usage: /usr/bin/perl5.00503 [switches] [--] [programfile] [arguments]
(略)
-Idirectory specify @INC/#include directory (may be used more than once)
私の使っている Linux マシンの一つに、とても古い個体がある。そのマシンに最初から入っている古い Perl と共存させるかたちで最新の Perl をインストールしたい。
ただ、Perl が古いのが問題だ。OS と一緒にインストールされた Perl 5.005_03 しか入っていない。新しいライブラリを使いたいのに、バージョンが古いせいでその恩恵にあずかれない。最新の Perl をインストールしたいところだが、古い Perl を単純に新しいバージョンの Perl で置き換えてしまうと、既存のスクリプトやツールが動かなくなってしまうかもしれない。というわけで、もとの 5.005_03 を残しつつ Perl 5.8.6 を別途インストールすることにした。
ひとまず Perl のソースコード一式をダウンロードしてこよう。今の Perl の安定版の最新のバージョンってなんだっけ? perl.com http://perl.com/ を見てみると、Stable is 5.8.6. という表示があった。うわー、ちょっと前に 5.8.1 が出たと思ったら、もう 5.8.6 まで出てるのか。
RingServer から Perl 5.8.6 のソースアーカイブをダウンロード。perl.com からダウンロードしてもよかったんだけど、こういう人気のありそうなサイトには負荷をかけちゃいけないよね。日本には RingServer があるんだし、そっちからダウンロードしよう。
Index of /archives/lang/perl/CPAN/src
http://ring.pwd.ne.jp/archives/lang/perl/CPAN/src/
上記ミラーサイトから、perl-5.8.6.tar.bz2 をダウンロードした。
古い perl は /usr/bin にインストールされているから、新しい perl は /usr/local などのディレクトリに入れてやればいい。とりあえずバイナリとライブラリ群を共存させるだけなら、これで十分かな。そうだ、今後 5.8.6 以外のバージョンをインストールするかもしれないから、/usr/local/perl に入れる事にしよう。perl のパスは /usr/local/perl/bin/perl になっちゃってかっこ悪いけど、まあいいでしょ。他にも、man page のインストール先なども指定できるようだけど、今回はデフォルトのままとした。
しかし、こんなに質問項目が多いと対話形式で設定するのは大変だな。一気に設定する方法はないかなー。INSTALL を読んでみる。なになに、Configure に -d オプションを渡せば質問にすべてデフォルトで答えてくれると。それは便利だ。あと、-D を使えば設定する値を引数として Configure に渡せる。じゃあこうすればいいんだな。
Configure と make と make test まで。
configure と コンパイルを含の所要時間がどれくらいかを知りたかったので、実際には以下のようにした。
これであとは終わるのを待つだけ。生成された config.sh というファイルが Configure の結果を格納した設定ファイルだそうだ。
なんだか make test がうまくいってないみたいだけど・・・。LD_LIBRARY_PATH を設定しろって? make test まで時間かかるので、またあとでやってみよう。
実行したマシンのスペック。
ちなみに、Configure と make と make テストを Pentium4 3.0GHz HyperThreading ON + 2048MB Memory のマシンで同じように実行したところ、8分弱で終わった。さすがに速いな。make test でもエラー出なかったし。
- 私の使っている古い Linux マシン
OS は TurboLinux Server 6.5。Pentium 200MHz に 128MB の SDRAM、4GB + 3GB + 8GB で合計15GB のディスク、DEC の Tulip LAN カード。今となってはまったくパワー不足に見えるかもしれない。しかし、ハードウェアが古くても、ソフトウェアが新しければ問題ない。常用するアプリケーションは新しめのものを入れてあるし、Ruby は 1.8.1 をインストールしてある。このマシンでは MovableType も動いている。要は使い方だ。ただ、Perl が古いのが問題だ。OS と一緒にインストールされた Perl 5.005_03 しか入っていない。新しいライブラリを使いたいのに、バージョンが古いせいでその恩恵にあずかれない。最新の Perl をインストールしたいところだが、古い Perl を単純に新しいバージョンの Perl で置き換えてしまうと、既存のスクリプトやツールが動かなくなってしまうかもしれない。というわけで、もとの 5.005_03 を残しつつ Perl 5.8.6 を別途インストールすることにした。
- 複数バージョンを共存させつつインストールするには?
複数バージョンを共存させつつインストールするにはどうしたらいいんだろう? 単純にパスを別にしておけばいいのかな。たぶん configure スクリプトでインストール先ディレクトリを指定することができるだろうから、それを既存の Perl と別にしてやればいいよね。ひとまず Perl のソースコード一式をダウンロードしてこよう。今の Perl の安定版の最新のバージョンってなんだっけ? perl.com http://perl.com/ を見てみると、Stable is 5.8.6. という表示があった。うわー、ちょっと前に 5.8.1 が出たと思ったら、もう 5.8.6 まで出てるのか。
RingServer から Perl 5.8.6 のソースアーカイブをダウンロード。perl.com からダウンロードしてもよかったんだけど、こういう人気のありそうなサイトには負荷をかけちゃいけないよね。日本には RingServer があるんだし、そっちからダウンロードしよう。
Index of /archives/lang/perl/CPAN/src
http://ring.pwd.ne.jp/archives/lang/perl/CPAN/src/
perl-5.8.6.tar.bz2 28-Nov-2004 08:56 9.2M
上記ミラーサイトから、perl-5.8.6.tar.bz2 をダウンロードした。
stable.tar.bz2 28-Nov-2004 08:56 9.2Mこれも中身は同じなんだろうな。
- perl の Configure, make, make test
tar ball を展開してとりあえず Configure を実行してみる。対話形式で設定が進む。ひたすら enter キーを連打。途中で、インストール先の prefix をどうするかを聞いてきた。これを指定してやればよさそうだ。By default, perl5 will be installed in /usr/local/bin, manual pages
under /usr/local/man, etc..., i.e. with /usr/local as prefix for all
installation directories. Typically this is something like /usr/local.
If you wish to have binaries under /usr/bin but other parts of the
installation under /usr/local, that's ok: you will be prompted
separately for each of the installation directories, the prefix being
only used to set the defaults.
Installation prefix to use? (~name ok) [/usr/local]
古い perl は /usr/bin にインストールされているから、新しい perl は /usr/local などのディレクトリに入れてやればいい。とりあえずバイナリとライブラリ群を共存させるだけなら、これで十分かな。そうだ、今後 5.8.6 以外のバージョンをインストールするかもしれないから、/usr/local/perl に入れる事にしよう。perl のパスは /usr/local/perl/bin/perl になっちゃってかっこ悪いけど、まあいいでしょ。他にも、man page のインストール先なども指定できるようだけど、今回はデフォルトのままとした。
しかし、こんなに質問項目が多いと対話形式で設定するのは大変だな。一気に設定する方法はないかなー。INSTALL を読んでみる。なになに、Configure に -d オプションを渡せば質問にすべてデフォルトで答えてくれると。それは便利だ。あと、-D を使えば設定する値を引数として Configure に渡せる。じゃあこうすればいいんだな。
Configure と make と make test まで。
tar --bzip2 -xvf perl-5.8.6.tar.bz2
cd perl-5.8.6/
sh Configure -Dprefix=/usr/local/perl -de && nice -19 make && make test
configure と コンパイルを含の所要時間がどれくらいかを知りたかったので、実際には以下のようにした。
time { sh Configure -Dprefix=/usr/local/perl -de && nice -19 make && make test; }
これであとは終わるのを待つだけ。生成された config.sh というファイルが Configure の結果を格納した設定ファイルだそうだ。
なんだか make test がうまくいってないみたいだけど・・・。LD_LIBRARY_PATH を設定しろって? make test まで時間かかるので、またあとでやってみよう。
Failed 1 test script out of 849, 99.88% okay.
### Since not all tests were successful, you may want to run some of
### them individually and examine any diagnostic messages they produce.
### See the INSTALL document's section on "make test".
### You have a good chance to get more information by running
### ./perl harness
### in the 't' directory since most (>=80%) of the tests succeeded.
### You may have to set your dynamic library search path,
### LD_LIBRARY_PATH, to point to the build directory:
### setenv LD_LIBRARY_PATH `pwd`:$LD_LIBRARY_PATH; cd t; ./perl harness
### LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH; export LD_LIBRARY_PATH; cd t; ./perl harness
### export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH; cd t; ./perl harness
### for csh-style shells, like tcsh; or for traditional/modern
### Bourne-style shells, like bash, ksh, and zsh, respectively.
u=27.54 s=3.1 cu=1924.17 cs=117.78 scripts=849 tests=87734
make[2]: *** [_test_tty] エラー 1
make[2]: 出ます ディレクトリ `/home/hiroaki/src/perl-5.8.6'
make[1]: *** [_test] エラー 2
make[1]: 出ます ディレクトリ `/home/hiroaki/src/perl-5.8.6'
make: *** [test] エラー 2
real 85m47.694s
user 74m29.910s
sys 4m37.180s
実行したマシンのスペック。
Linux version 2.2.18-2 (support@kernel.turbolinux.com) (gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)) #1 Wed Mar 14 12:38:41 JST 2001
Detected 200460 kHz processor.
Console: colour VGA+ 80x25
Calibrating delay loop... 399.76 BogoMIPS
Memory: 127408k/131072k available (1488k kernel code, 408k reserved, 1684k data, 84k init, 0k bigmem)
ちなみに、Configure と make と make テストを Pentium4 3.0GHz HyperThreading ON + 2048MB Memory のマシンで同じように実行したところ、8分弱で終わった。さすがに速いな。make test でもエラー出なかったし。
real 7m47.858s
user 4m20.804s
sys 0m56.577s
2004-10-11 の「cygwin の Perl から Windows のMySQL に接続」で書いたように、今までは Cygwin 環境だったので MySQL 接続ドライバに Net::MySQL を使っていた。今回 ActivePerl を入れたので DBI + DBD が使えるようになるはず。Google で DBD DBI MySQL 設定を検索すると、perldoc.jp の文書がヒット。
INSTALL - DBD::mysqlのインストールと構成設定の方法
http://perldoc.jp/docs/modules/DBD-mysql-2.1026/DBD/mysql/IN ...
スタートメニューの ActiveState ActivePerl 5.8 から Perl Package Manager を起動し、install DBI を入力。あとは勝手にインストールされた。これは簡単だ。
INSTALL - DBD::mysqlのインストールと構成設定の方法
http://perldoc.jp/docs/modules/DBD-mysql-2.1026/DBD/mysql/IN ...
ActivePerl は DBD::mysqlのPPMアーカイブを提供しています。あなたに必要なことは以下のように打ち込むだけです
ppm
install DBI
install DBD-mysql
スタートメニューの ActiveState ActivePerl 5.8 から Perl Package Manager を起動し、install DBI を入力。あとは勝手にインストールされた。これは簡単だ。
ppm> install DBI
====================
Install 'DBI' version 1.43 in ActivePerl 5.8.4.810.
====================
Downloaded 513616 bytes.
Extracting 69/69: blib/arch/auto/DBI/Driver_xst.h
Installing C:\usr\site\lib\auto\DBI\dbd_xsh.h
Installing C:\usr\site\lib\auto\DBI\DBI.bs
Installing C:\usr\site\lib\auto\DBI\DBI.dll
Installing C:\usr\site\lib\auto\DBI\DBI.exp
Installing C:\usr\site\lib\auto\DBI\DBI.lib
Installing C:\usr\site\lib\auto\DBI\dbipport.h
Installing C:\usr\site\lib\auto\DBI\dbivport.h
Installing C:\usr\site\lib\auto\DBI\DBIXS.h
Installing C:\usr\site\lib\auto\DBI\dbi_sql.h
Installing C:\usr\site\lib\auto\DBI\Driver.xst
Installing C:\usr\site\lib\auto\DBI\Driver_xst.h
Installing C:\usr\html\bin\dbiprof.html
Installing C:\usr\html\bin\dbiproxy.html
Installing C:\usr\html\site\lib\DBI.html
Installing C:\usr\html\site\lib\Bundle\DBI.html
Installing C:\usr\html\site\lib\DBD\DBM.html
Installing C:\usr\html\site\lib\DBD\File.html
Installing C:\usr\html\site\lib\DBD\Proxy.html
Installing C:\usr\html\site\lib\DBD\Sponge.html
Installing C:\usr\html\site\lib\DBI\Changes.html
Installing C:\usr\html\site\lib\DBI\DBD.html
Installing C:\usr\html\site\lib\DBI\FAQ.html
Installing C:\usr\html\site\lib\DBI\Profile.html
Installing C:\usr\html\site\lib\DBI\ProfileData.html
Installing C:\usr\html\site\lib\DBI\ProfileDumper.html
Installing C:\usr\html\site\lib\DBI\ProxyServer.html
Installing C:\usr\html\site\lib\DBI\PurePerl.html
Installing C:\usr\html\site\lib\DBI\W32ODBC.html
Installing C:\usr\html\site\lib\DBI\Const\GetInfoReturn.html
Installing C:\usr\html\site\lib\DBI\Const\GetInfoType.html
Installing C:\usr\html\site\lib\DBI\Const\GetInfo\ANSI.html
Installing C:\usr\html\site\lib\DBI\Const\GetInfo\ODBC.html
Installing C:\usr\html\site\lib\DBI\DBD\Metadata.html
Installing C:\usr\html\site\lib\DBI\ProfileDumper\Apache.html
Installing C:\usr\html\site\lib\DBI\SQL\Nano.html
Installing C:\usr\html\site\lib\Win32\DBIODBC.html
Files found in blib\arch: installing files in blib\lib into architecture depnt library tree
Installing C:\usr\site\lib\DBI.pm
Installing C:\usr\site\lib\Bundle\DBI.pm
Installing C:\usr\site\lib\DBD\DBM.pm
Installing C:\usr\site\lib\DBD\ExampleP.pm
Installing C:\usr\site\lib\DBD\File.pm
Installing C:\usr\site\lib\DBD\NullP.pm
Installing C:\usr\site\lib\DBD\Proxy.pm
Installing C:\usr\site\lib\DBD\Sponge.pm
Installing C:\usr\site\lib\DBI\Changes.pm
Installing C:\usr\site\lib\DBI\DBD.pm
Installing C:\usr\site\lib\DBI\FAQ.pm
Installing C:\usr\site\lib\DBI\Profile.pm
Installing C:\usr\site\lib\DBI\ProfileData.pm
Installing C:\usr\site\lib\DBI\ProfileDumper.pm
Installing C:\usr\site\lib\DBI\ProxyServer.pm
Installing C:\usr\site\lib\DBI\PurePerl.pm
Installing C:\usr\site\lib\DBI\W32ODBC.pm
Installing C:\usr\site\lib\DBI\Const\GetInfoReturn.pm
Installing C:\usr\site\lib\DBI\Const\GetInfoType.pm
Installing C:\usr\site\lib\DBI\Const\GetInfo\ANSI.pm
Installing C:\usr\site\lib\DBI\Const\GetInfo\ODBC.pm
Installing C:\usr\site\lib\DBI\DBD\Metadata.pm
Installing C:\usr\site\lib\DBI\ProfileDumper\Apache.pm
Installing C:\usr\site\lib\DBI\SQL\Nano.pm
Installing C:\usr\site\lib\Win32\DBIODBC.pm
Installing C:\usr\bin\dbiprof
Installing C:\usr\bin\dbiprof.bat
Installing C:\usr\bin\dbiproxy
Installing C:\usr\bin\dbiproxy.bat
Successfully installed DBI version 1.43 in ActivePerl 5.8.4.810.
ppm> install DBD-mysql
====================
Install 'DBD-mysql' version 2.9003 in ActivePerl 5.8.4.810.
====================
Downloaded 178968 bytes.
Extracting 17/17: blib/arch/auto/DBD/mysql/mysql.lib
Installing C:\usr\site\lib\auto\DBD\mysql\mysql.bs
Installing C:\usr\site\lib\auto\DBD\mysql\mysql.dll
Installing C:\usr\site\lib\auto\DBD\mysql\mysql.exp
Installing C:\usr\site\lib\auto\DBD\mysql\mysql.lib
Installing C:\usr\html\site\lib\Mysql.html
Installing C:\usr\html\site\lib\Bundle\DBD\mysql.html
Installing C:\usr\html\site\lib\DBD\mysql.html
Installing C:\usr\html\site\lib\DBD\mysql\INSTALL.html
Files found in blib\arch: installing files in blib\lib into architecture depnt library tree
Installing C:\usr\site\lib\Mysql.pm
Installing C:\usr\site\lib\Bundle\DBD\mysql.pm
Installing C:\usr\site\lib\DBD\mysql.pm
Installing C:\usr\site\lib\DBD\mysql\GetInfo.pm
Installing C:\usr\site\lib\DBD\mysql\INSTALL.pod
Installing C:\usr\site\lib\Mysql\Statement.pm
Successfully installed DBD-mysql version 2.9003 in ActivePerl 5.8.4.810.
List::Util は配列の最大値、最小値、合計値を取得したり、配列をシャッフルしたりするためのルーチンを集めたPerl モジュール。Perl 標準ではなかったが結構前からあったモジュールらしい。Perl 5.8 から標準ライブラリとして組み込まれたようだ。Scalar-List-Utils が元なのかな?
私ははっきり言って全く知らなかった。あると便利だけどこのくらいなら自分で書いちゃうし、Google で List::Util を検索してもあまりヒットしないしね。
日本語で読める文書は以下のサイトが一番詳しい。
Hash::Util - 便利な配列用関数のピックアップ
http://fleur.hio.jp/~hio/perldoc/mix/lib/List/Util.html
以下、よく使いそうなメソッドと実行結果のサンプル。ワンライナー (One Liner) だけど。
first BLOCK LIST
maxstr LIST
minstr LIST
reduce BLOCK LIST
最初に説明を読んだときは、これで何をしたいの? と思った。要するに foreach を使わずに、配列要素同士をいろいろ操作できるんだね。慣れると便利かも。
つまり、use するときは明示的に使うメソッドを記述するか、名前空間付きでメソッドを記述する必要がある。
明示しないと以下のようになる。
エクスポートしない理由は不明。既存のライブラリとメソッド名が一緒になるから? それじゃあ何のために名前空間があるのかわからないし。
私ははっきり言って全く知らなかった。あると便利だけどこのくらいなら自分で書いちゃうし、Google で List::Util を検索してもあまりヒットしないしね。
日本語で読める文書は以下のサイトが一番詳しい。
Hash::Util - 便利な配列用関数のピックアップ
http://fleur.hio.jp/~hio/perldoc/mix/lib/List/Util.html
以下、よく使いそうなメソッドと実行結果のサンプル。ワンライナー (One Liner) だけど。
- 配列の要素のうちの最大値を取得
max LIST$ perl -MList::Util -e 'print List::Util::max(0..9);'
9
- 配列の要素のうちの最小値を取得
min LIST$ perl -MList::Util -e 'print List::Util::min(0..9);'
0
- 配列の要素の合計値を取得
sum LIST$ perl -MList::Util -e 'print List::Util::sum(0..9);'
45
- 配列のシャッフル
shuffle LIST$ perl -MList::Util -e 'print List::Util::shuffle(0..9);'
1697803452
- その他
あんまり使わなそうなものたち。first BLOCK LIST
LIST の要素を順番に $_ に設定して BLOCK を評価する点で grep と似ています. first は BLOCK が真と評価した最初の要素を返します. BLOCK が一度も真を返さなかったり LIST が空だったときには undef を返します.
$foo = first { defined($_) } @list # first defined value in @list
# @list で最初に定義されている値.
$foo = first { $_ > $value } @list # first value in @list which
# is greater than $value
# @list の中で $value より大きい
# 最初の値.
maxstr LIST
max と似ていますが, 全ての要素は文字列として処理され, gt によって一番大きいと評価された文字列を返します
minstr LIST
min と似ていますが, 全ての要素は文字列として処理され, lt によって一番小さいと評価された文字列を返します.
reduce BLOCK LIST
最初に説明を読んだときは、これで何をしたいの? と思った。要するに foreach を使わずに、配列要素同士をいろいろ操作できるんだね。慣れると便利かも。
BLOCK を複数回呼び出して LIST を減少させます. 毎回 $a 及び $b が設定されます. 最初の呼び出しでは $a と $b にはリストの最初の2つの要素が設定されます. その後の呼び出しでは $a に1つ前の呼び出しでの復帰値が, $b にはリストの次の要素が設定されます.
最後の BLOCK の呼び出しの復帰値が返されます. もし LIST が空だったときには undef が返されます. LIST が1つしか値を持っていなかったときには BLOCK を実行することなしにその要素が返されます.
$foo = reduce { $a < $b ? $a : $b } 1..10 # min
$foo = reduce { $a lt $b ? $a : $b } 'aa'..'zz' # minstr
$foo = reduce { $a + $b } 1 .. 10 # sum
$foo = reduce { $a . $b } @bar # concat
- List::Util はメソッドをエクスポートしない
List::Util はメソッドをエクスポートしない。By default List::Util does not export any subroutines.
つまり、use するときは明示的に使うメソッドを記述するか、名前空間付きでメソッドを記述する必要がある。
use List::Util qw(shuffle);
明示しないと以下のようになる。
$ perl -MList::Util -e 'print shuffle(0..9);'
Undefined subroutine &main::shuffle called at -e line 1.
エクスポートしない理由は不明。既存のライブラリとメソッド名が一緒になるから? それじゃあ何のために名前空間があるのかわからないし。
- Hash::Util
Hash::Util というものもあるが、ドキュメントを読むと以下のメソッドしか用意されてない。キーや値を追加できなくしたり、削除できなくしたりするらしい。あんまり使わないだろうなあ。get アクセサしか用意しないメンバを作りたいといった、オブジェクト指向的なアクセス制御に使ったりするのかな?use Hash::Util qw(lock_keys unlock_keys
lock_value unlock_value
lock_hash unlock_hash);
Perl で配列をシャッフルする方法。Perl クックブックに「配列のランダマイズ」として載ってたけどメモ。自分のライブラリにもあるけど、ブラウザからさくっとコピー & ペーストできと便利だしね。
mixi perlならではの便利な短いコードを書き留めたい
http://mixi.jp/view_bbs.pl?id=2041
Perl 5.8 から標準ライブラリになった List::Util を使う方法もある。2004-11-14 の「List::Util Perl 標準の配列操作ライブラリ」を参照。
mixi perlならではの便利な短いコードを書き留めたい
http://mixi.jp/view_bbs.pl?id=2041
2004年11月12日 15:03 32: あとむ
#=========================
# 配列を混ぜる
#=========================
# @list=shuffle (@list);
sub shuffle {
my @list =@_;
for my $i ( 0..$#list ) {
my $rand=int(rand(@list));
my $tmp=$list[$i];
$list[$i]=$list[$rand];
$list[$rand]=$tmp;
}
@list
}
2004年11月12日 17:28 33: jjx
>>あとむさん
標準モジュールにList::Utilというものがあってその中にshuffleはありますよ
其れはおいておいて自分なりにかいてみました
sub shuffle {
my @list;
push( @list, splice(@_, int(rand(@_))) ) while(@_);
return @list;
}
Perlクックブック〈VOLUME1〉
トム クリスチャンセン / ネイザン トーキントン / Tom Christiansen / Nathan Torkington / Shibuya Perl Mongers / ドキュメントシステム
発売日: 2004/09
amazon で詳しく見る
トム クリスチャンセン / ネイザン トーキントン / Tom Christiansen / Nathan Torkington / Shibuya Perl Mongers / ドキュメントシステム
発売日: 2004/09
amazon で詳しく見る
Perlクックブック〈VOLUME2〉
トム クリスチャンセン / ネイザン トーキントン / Tom Christiansen / Nathan Torkington / Shibuya Perl Mongers / ドキュメントシステム
発売日: 2004/09
amazon で詳しく見る
トム クリスチャンセン / ネイザン トーキントン / Tom Christiansen / Nathan Torkington / Shibuya Perl Mongers / ドキュメントシステム
発売日: 2004/09
amazon で詳しく見る
- List::Util を使った配列のシャッフル
2004-11-14 追記。Perl 5.8 から標準ライブラリになった List::Util を使う方法もある。2004-11-14 の「List::Util Perl 標準の配列操作ライブラリ」を参照。
$ perl -MList::Util -e 'print List::Util::shuffle(0..9);'
1697803452
Windows マシン上にある MySQL 4.0.20 に cygwin の Perl 5.8.5 から接続したい。
CPAN から DBD と DBI をインストールすれば良いと思ったが make 中にエラー。そのプラットホーム用の MySQL のインクルードファイルがないとダメなようだ。
cygwin 用 に MySQL をインストールすればいいのかもしれないが、それはやりたくない。せっかく Windows ネイティブ版があるんだから。
Net::MySQL - Perlだけで書かれたMySQLネットワークプロトコルへのインターフェイス
http://perldoc.jp/docs/modules/Net-MySQL-0.05/MySQL.pod
ありがたい。これを使うことで無事接続できた。よかったー。
INSTALL - DBD::mysqlのインストールと構成設定の方法
http://perldoc.jp/docs/modules/DBD-mysql-2.1026/DBD/mysql/IN ...
CPAN から DBD と DBI をインストールすれば良いと思ったが make 中にエラー。そのプラットホーム用の MySQL のインクルードファイルがないとダメなようだ。
CPAN.pm: Going to build R/RU/RUDY/DBD-mysql-2.9004.tar.gz
Can't exec "mysql_config": No such file or directory at Makefile.PL line 174.
readline() on closed filehandle PIPE at Makefile.PL line 176.
Can't exec "mysql_config": No such file or directory at Makefile.PL line 174.
readline() on closed filehandle PIPE at Makefile.PL line 176.
Can't exec "mysql_config": No such file or directory at Makefile.PL line 174.
readline() on closed filehandle PIPE at Makefile.PL line 176.
Can't exec "mysql_config": No such file or directory at Makefile.PL line 174.
readline() on closed filehandle PIPE at Makefile.PL line 176.
Can't exec "mysql_config": No such file or directory at Makefile.PL line 174.
readline() on closed filehandle PIPE at Makefile.PL line 176.
Failed to determine directory of mysql.h. Use
cygwin 用 に MySQL をインストールすればいいのかもしれないが、それはやりたくない。せっかく Windows ネイティブ版があるんだから。
- Pure perl MySQL インターフェイス
以下を見つけた。Net::MySQL - Perlだけで書かれたMySQLネットワークプロトコルへのインターフェイス
http://perldoc.jp/docs/modules/Net-MySQL-0.05/MySQL.pod
Net::MySQLはPerlだけで記述されたMySQLデータベースへのクライアントインターフェイスです。MySQLのサーバとクライアント間で利用されているネットワークプロトコルを独自に実装しており、libmysqlclientなどMySQLのライブラリが無くても動作します。つまりMySQLが移植されていないOSからでもMySQLサーバに接続することができるのです。イカスッ!
ありがたい。これを使うことで無事接続できた。よかったー。
- 2004年10月12日 追記
cygwin 環境でのセットアップについて細かく解説してある。INSTALL - DBD::mysqlのインストールと構成設定の方法
http://perldoc.jp/docs/modules/DBD-mysql-2.1026/DBD/mysql/IN ...
apache には 環境変数を表示する CGI printenv.cgi が付いてくるが、レンタルサーバなどでは残念ながら削除されてしまっていることが多い。printenv.cgi は環境変数を表示しつつ .htaccess や cgi の調整や動作確認を行いたいときに重宝するのでメモ。
以下を printenv.cgi などといったファイル名で保存し、適切なパーミッションを与えてブラウザからアクセス。
実行結果。HTTP_ACCEPT の行は長すぎるので、ペースト時に改行を入れた。
PHP が使えるんだったら <?php phpinfo(); ?> を見た方が早いかも。
以下を printenv.cgi などといったファイル名で保存し、適切なパーミッションを与えてブラウザからアクセス。
#!/usr/local/bin/perl
##
## printenv -- demo CGI program which just prints its environment
##
print "Content-type: text/plain\n\n";
foreach $var (sort(keys(%ENV))) {
$val = $ENV{$var};
$val =~ s|\n|\\n|g;
$val =~ s|"|\\"|g;
print "${var}=\"${val}\"\n";
}
実行結果。HTTP_ACCEPT の行は長すぎるので、ペースト時に改行を入れた。
DOCUMENT_ROOT=/usr/local/etc/httpd/htdocs
GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=text/xml,application/xml,application/xhtml+xml,
text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
HTTP_ACCEPT_CHARSET=Shift_JIS,utf-8;q=0.7,*;q=0.7
HTTP_ACCEPT_ENCODING=gzip,deflate
HTTP_ACCEPT_LANGUAGE=ja,en-us;q=0.7,en;q=0.3
HTTP_CONNECTION=keep-alive
HTTP_HOST=www.example.com
HTTP_KEEP_ALIVE=300
HTTP_USER_AGENT=Mozilla/5.0 (Windows; U; Windows NT 5.0; rv:1.7.3) Gecko/20040913 Firefox/0.10
PATH=/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin
QUERY_STRING=
REMOTE_ADDR=192.168.0.1
REMOTE_PORT=6209
REQUEST_METHOD=GET
REQUEST_URI=/cgi-bin/printenv.cgi
SCRIPT_FILENAME=/usr/local/etc/httpd/cgi-bin/printenv.cgi
SCRIPT_NAME=/cgi-bin/i16/printenv.cgi
SERVER_ADDR=192.168.0.2
SERVER_ADMIN=webmaster@example.com
SERVER_NAME=www.example.com
SERVER_PORT=80
SERVER_PROTOCOL=HTTP/1.1
SERVER_SIGNATURE=
SERVER_SOFTWARE=Apache/1.3.31 (Unix)
USER=#-1
PHP が使えるんだったら <?php phpinfo(); ?> を見た方が早いかも。
コマンドラインから添付ファイル付きメールを送信したい。cron で定期的にメール送信するバッチが必要になった。
エラーを追求する手間をかけるよりも、今回は他の解決策を探した方が良いと私のゴーストがささやいている。「From の指定」など、mpack には無い機能も使いたいし。というわけで、今回は Perl か Ruby で SMTP と Content-Type: multipart/mixed; を扱うスクリプトを書くことにしよう。
ActivePerlからメールを送る
http://member.nifty.ne.jp/hippo2000/perltips/perlmail.htm
さすがは Perl、全くスクリプトを書かなくてもやりたいことを達成できてしまった。
Google で ruby 添付ファイル メール 送信を検索。なんだかあまりヒットしないな。こういう細かい仕事を自動化するスクリプトの需要は結構あると思うんだけどな。検索語が悪いのかも。英語でやってみよう。Google で ruby base64 mail attachment を検索。[ruby-list:30312] composing a mail with a big file attached がヒット。なるほど、Tmail という便利なライブラリがあるんだね。
TMail
http://www.loveruby.net/ja/prog/tmail.html
オフィシャルサイトの説明には「Ruby 用 メール総合ライブラリ」って書いてある。かなり高機能なライブラリのようだ。あ、Tmail の作者は 「Rubyソースコード完全解説」や「Ruby レシピブック」で有名な青木さんだ。
tar を展開して、
setup.rb config
setup.rb setup
setup.rb install
を実行するだけ。
コンパイルの時にいろいろエラーがでたけど、セットアップスクリプトが ignore するよって言ってるんだから気にしないことにする。
さて、この便利なライブラリ Tmail を使えば、簡単に添付ファイル付きメール送信スクリプトが書けるだろう。でも、やっぱりだれかが同じようなスクリプトを書いているはず。それを探して使うほうが早いだろう。ウェブを探してみると、高林さんが書いた「vCard ファイルを添付したメールを作る」が Tmail を使っている。短いし、これをちょっと修正すればやりたいことは達成できそう。
vCard ファイルを添付したメールを作る
http://namazu.org/~satoru/attic/vcardmail.rb
- 2003-12-12 でも同じ記事を書いたけど
2003-12-12 にも同じ「添付ファイル付きメールをコマンドラインから送信」という記事を書いたが、あれは Linux での話だ。今回は cygwin で同じことをやりたい。本当は前回の記事でも使った mpack を cygwin 環境でコンパイルして使おうと思ったんだけど、コンパイルエラーとなってしまった。エラーを追求する手間をかけるよりも、今回は他の解決策を探した方が良いと私のゴーストがささやいている。「From の指定」など、mpack には無い機能も使いたいし。というわけで、今回は Perl か Ruby で SMTP と Content-Type: multipart/mixed; を扱うスクリプトを書くことにしよう。
- Perl で添付ファイル付きメールを送信
Perl であれば、すでに添付ファイル付きメール送信スクリプトのサンプルが河馬屋二千年堂のウェブサイトにあったはずなので、それを使えばいい。このサンプルを動かすには、いくつか必要なモジュールがある。その部分だけ引用しておく。ActivePerlからメールを送る
http://member.nifty.ne.jp/hippo2000/perltips/perlmail.htm
添付ファイル付のメールを送信する + 日本語への対応+HTMLメール
use Net::SMTP;
use MIME::Entity;
use MIME::Words qw (:all);
require 'jcode.pl';
さすがは Perl、全くスクリプトを書かなくてもやりたいことを達成できてしまった。
- RUby で添付ファイル付きメールを送信
Ruby は標準インストールされるライブラリが充実してるから、もしかしたら smtp や base64 を扱うクラス、果ては添付ファイル付きメール送信クラスなんてものまで標準で用意されてるかも。ちょっと期待。Google で ruby 添付ファイル メール 送信を検索。なんだかあまりヒットしないな。こういう細かい仕事を自動化するスクリプトの需要は結構あると思うんだけどな。検索語が悪いのかも。英語でやってみよう。Google で ruby base64 mail attachment を検索。[ruby-list:30312] composing a mail with a big file attached がヒット。なるほど、Tmail という便利なライブラリがあるんだね。
TMail
http://www.loveruby.net/ja/prog/tmail.html
オフィシャルサイトの説明には「Ruby 用 メール総合ライブラリ」って書いてある。かなり高機能なライブラリのようだ。あ、Tmail の作者は 「Rubyソースコード完全解説」や「Ruby レシピブック」で有名な青木さんだ。
- Tmail のインストール
cygwin でも無事インストールできた。tar を展開して、
setup.rb config
setup.rb setup
setup.rb install
を実行するだけ。
コンパイルの時にいろいろエラーがでたけど、セットアップスクリプトが ignore するよって言ってるんだから気にしないことにする。
さて、この便利なライブラリ Tmail を使えば、簡単に添付ファイル付きメール送信スクリプトが書けるだろう。でも、やっぱりだれかが同じようなスクリプトを書いているはず。それを探して使うほうが早いだろう。ウェブを探してみると、高林さんが書いた「vCard ファイルを添付したメールを作る」が Tmail を使っている。短いし、これをちょっと修正すればやりたいことは達成できそう。
vCard ファイルを添付したメールを作る
http://namazu.org/~satoru/attic/vcardmail.rb
- スクリプト完成、いざメール送信
高林さんのスクリプトほとんどそのまま。元のスクリプトが GPL2 なので、以下のスクリプトのライセンスも GPL2。#!/usr/bin/env ruby
require 'kconv'
require 'tmail'
require 'net/smtp'
def generate_filename (vcard)
/^FN:(.*)/ =~ vcard
name = $1
"=?ISO-2022-JP?B?" + (name + '.vcf').tojis.to_a.pack('m').chomp + "?="
end
raise unless ARGV.length == 2
to = ARGV.shift
vcard = File.open(ARGV.shift).readlines.join('')
mail = TMail::Mail.new
mail.to = to
mail.from = 'example@example.com'
mail.subject = "vcard"
mail.date = Time.now
mail.mime_version = '1.0'
message = TMail::Mail.new
message.set_content_type('text', 'plain')
message.transfer_encoding = '7bit'
message.body = "vcard is attached.\n"
filename = 'atch'
encoded_vcard = [vcard].pack('m').chomp.gsub(/.{76}/, "\\1\n")
attachment = TMail::Mail.new
attachment.body = encoded_vcard
attachment.transfer_encoding = 'base64'
attachment.set_content_type('text', 'x-vcard', 'name' => filename)
attachment.set_content_disposition('attachment',
'filename' => filename)
mail.parts.push(message)
mail.parts.push(attachment)
mail.write_back
$smtp_server = '10.83.0.38'
Net::SMTP.start($smtp_server) do |smtp|
smtp.sendmail(mail.encoded, mail.from, to)
end
- 2004年7月7日追記
って、あれ? この記事はもう少し加筆してから公開しようかと思ってたんだけど、いつの間にか公開状態になってる。まあいいや。上記スクリプトも変数名を直したり、複数添付ファイルに対応しようと思ってた。もしかしたら全く動かないかもしれない。そのうち直す予定。- 2004-10-09 追記
Ruby 1.8 の標準ライブラリだけで動作する添付ファイル付きメール送信スクリプトを 2004-10-09 に書いた。今日アクセス解析ページを見ていたら、2004年1月分の記録が無くなっていることに気づいた。
このページに付けている shinobi アクセス解析は、データを4か月分しか保存してくれない。このため、2004年1月分のカウントが消えてしまった。仕方がないので、アクセスカウンタを付けることにした。ただ、一般ユーザはそのページのヒット数なんて気にしないだろうから、管理者だけが数字を閲覧できるタイプのカウンタで十分だ。
この程度の要件ならすぐ作れそうと思ったので、アクセスカウンタスクリプトを自作してみた。単に数を数えられればそれでいいので、かなり簡素。flock による排他制御は考慮したけど、ファイル I/O エラー時の処理を組み込んでいないなど、かなり思い切った実装。
カウント時の画像は、perl スクリプト中に png をシリアライズして埋め込んでおく。png はサイズを小さくするために、1 ピクセル * 1ピクセルの 白黒画像とし、url エンコードして文字列化する。base64 でもいいけど、デコードルーチンが url エンコードに比べて長いし、Perl モジュールも標準で入ってない環境があるので今回は見送り。
url エンコード。スペースを + に変換する部分は省略。
スクリプトに組み込み。
html に記述して準備完了。
最後に、shinobi がいままでカウントしてくれた Page View を count.dat に記述して完了。
閲覧は http://sonic64.hp.infoseek.co.jp/cgi-bin/c.cgi/view から。infoseek の広告が入っちゃってるけど、まあ気にしないことにする。
このページに付けている shinobi アクセス解析は、データを4か月分しか保存してくれない。このため、2004年1月分のカウントが消えてしまった。仕方がないので、アクセスカウンタを付けることにした。ただ、一般ユーザはそのページのヒット数なんて気にしないだろうから、管理者だけが数字を閲覧できるタイプのカウンタで十分だ。
この程度の要件ならすぐ作れそうと思ったので、アクセスカウンタスクリプトを自作してみた。単に数を数えられればそれでいいので、かなり簡素。flock による排他制御は考慮したけど、ファイル I/O エラー時の処理を組み込んでいないなど、かなり思い切った実装。
- ifame によるテキストアクセスカウンタ
iframe で呼び出して使う。アクセスされればされただけ計数する。#!/usr/bin/perl
open(OUT, '+< /home/hiroaki/cgi-data/count.dat');
flock(OUT, 2);
my $cnt = <OUT>;
++$cnt;
seek(OUT, 0, 0);
print OUT $cnt;
truncate(OUT, tell(OUT));
close(OUT);
print "Content-Type: text/plain\n\n$cnt\n";
- img src で呼び出せる画像カウンタ
iframe よりも img src で呼び出してカウントできる画像カウンタの方がいいかな。カウント時は画像を返し、数字の閲覧時はテキストを返すカウンタにしよう。0から9までの数字部品を呼び出して数字文字列を組み上げるタイプの画像カウンタは手間がかかるので、カウント時はごく小さな単一の画像を返すだけとする。要するに Web バグと呼ばれるものだ。カウント時の画像は、perl スクリプト中に png をシリアライズして埋め込んでおく。png はサイズを小さくするために、1 ピクセル * 1ピクセルの 白黒画像とし、url エンコードして文字列化する。base64 でもいいけど、デコードルーチンが url エンコードに比べて長いし、Perl モジュールも標準で入ってない環境があるので今回は見送り。
url エンコード。スペースを + に変換する部分は省略。
$ cat count.png |perl -pe 's/([^\w ])/"%".unpack("H2", $1)/eg;'
%89PNG%0d%0a%1a%0a%00%00%00%0dIHDR%00%00%00%01%00%00%00%01%01%00%00%00%007n%f9%24%00%00%00%0aIDAT%18%d3ch%00%00%00%82%00%81%a7%01%ba%10%00%00%00%00IEND%aeB%60%82
スクリプトに組み込み。
#!/usr/bin/perl
open(OUT, '+< /home/hiroaki/cgi-data/count.dat');
flock(OUT, 2);
my $cnt = <OUT>;
++$cnt;
seek(OUT, 0, 0);
print OUT $cnt;
truncate(OUT, tell(OUT));
close(OUT);
my $img = '%89PNG%0d%0a%1a%0a%00%00%00%0dIHDR%00%00%00%01%00%00%00%01%01%00%00%00%007n%f9%24%00%00%00%0aIDAT%18%d3ch%00%00%00%82%00%81%a7%01%ba%10%00%00%00%00IEND%aeB%60%82';
$img =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg;
print "Content-Type: img/png\n\n$img\n";
html に記述して準備完了。
<img src="http://sonic64.hp.infoseek.co.jp/cgi-bin/c.cgi" height="0" width="0" alt="">
- 閲覧機能を組み込む
あ、そうだ。やっぱり数字の閲覧もできた方がいいな。別の cgi にしてもいいけど、この程度だったら path_info か Query String で分岐させるやり方で十分だね。#!/usr/bin/perl
my $cnt_file = '/home/hiroaki/cgi-data/count.dat';
my %result;
if (defined($ENV{PATH_INFO}) and ($ENV{PATH_INFO} eq '/view')) {
open(CNT, $cnt_file);
flock(CNT, 1);
my $cnt = <CNT>;
close(CNT);
$result{body} = $cnt;
$result{header} = 'text/plain';
} else {
open(CNT, "+< $cnt_file");
flock(CNT, 2);
my $cnt = <CNT>;
++$cnt;
seek(CNT, 0, 0);
print CNT $cnt;
truncate(OUT, tell(OUT));
close(CNT);
my $img = '%89PNG%0d%0a%1a%0a%00%00%00%0dIHDR%00%00%00%01%00%00%00%01%01%00%00%00%007n%f9%24%00%00%00%0aIDAT%18%d3ch%00%00%00%82%00%81%a7%01%ba%10%00%00%00%00IEND%aeB%60%82';
$img =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg;
$result{body} = $img;
$result{header} = 'image/png';
}
printf("Content-Type: %s\n\n%s", $result{header}, $result{body});
- 数字閲覧時も計数した方が良いかなあ
一般的なカウンタは、ページ管理者がカウンタの数字を閲覧する時にも計数するのが普通のようだ。それに習って、このカウンタも数字閲覧時にもカウントするようにした。また、キャッシュされないようにする HTTP レスポンスヘッダも出力するようにした。#!/usr/local/bin/perl
my $cnt_file = '../dat/count.dat';
my %result;
open(CNT, "+< $cnt_file");
flock(CNT, 2);
my $cnt = <CNT>;
++$cnt;
seek(CNT, 0, 0);
print CNT $cnt;
truncate(OUT, tell(OUT));
close(CNT);
if (defined($ENV{PATH_INFO}) and ($ENV{PATH_INFO} eq '/view')) {
$result{body} = $cnt;
$result{header} = 'text/plain';
} else {
my $img = '%89PNG%0d%0a%1a%0a%00%00%00%0dIHDR%00%00%00%01%00%00%00%01%01%00%00%00%007n%f9%24%00%00%00%0aIDAT%18%d3ch%00%00%00%82%00%81%a7%01%ba%10%00%00%00%00IEND%aeB%60%82';
$img =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg;
$result{body} = $img;
$result{header} = 'image/png';
}
my $response = <<"__HERE__";
Cache-Control: no-cache
Pragma: no-cache
__HERE__
$response .= sprintf("Content-Type: %s\n\n%s", $result{header}, $result{body});
print $response;
最後に、shinobi がいままでカウントしてくれた Page View を count.dat に記述して完了。
閲覧は http://sonic64.hp.infoseek.co.jp/cgi-bin/c.cgi/view から。infoseek の広告が入っちゃってるけど、まあ気にしないことにする。
このページにカテゴリ一覧を表示するようにしたが、大文字と小文字を同一視してくれない。一文字目が A から Z のカテゴリが並んだ後に、一文字目が a から z で始まるカテゴリが並ぶ、という状態になってる。これでも良いかなとも思ったのだが、chalow のカテゴリと ChangeLog メモ のカテゴリが離れてしまうのが痛い。それにやっぱり大文字小文字関係なしでアルファベット順に並んだ方が使いやすい気がする。Perl カテゴリを perl カテゴリと書いてしまったときもすぐに気づくし。
というわけで修正。要するに sort 関数に独自の比較関数を使わせるようにすればいい。
カテゴリ一覧を作る関数にある
というわけで修正。要するに sort 関数に独自の比較関数を使わせるようにすればいい。
カテゴリ一覧を作る関数にある
foreach my $cat (sort keys %category_item)を
foreach my $cat (sort{ lc $a cmp lc $b } keys %category_item)とした。
A のファイルには存在するが、B のファイルには存在しない行を見つける。ただし、「A のファイルには存在するが、B のファイルには存在しない行」を見つけることはできるが、「B には存在するが、A には存在しない」という行は検出できないので注意。まあ逆にしてもう一回やれば良いんだけど。
ただ単に配列がファイルになっただけ。確か同じことをやってくれるコマンドがあったような気もするけど、見つけられなかったので書いた。エラー処理とかは考慮してない。
ただ単に配列がファイルになっただけ。確か同じことをやってくれるコマンドがあったような気もするけど、見つけられなかったので書いた。エラー処理とかは考慮してない。
- コード
#!/usr/bin/perl -w
use strict;
my $part_file = $ARGV[0];
my $all_file = $ARGV[1];
open(PART, $part_file);
my @part = <PART>;
close(PART);
open(ALL, $all_file);
my @all = <ALL>;
close(ALL);
my %seen;
my @part_only;
@seen{@all} = ();
foreach my $item (@part) {
push(@part_only, $item) unless exists $seen{$item};
$seen{$item} = 1;
}
print @part_only;
printf("all: %d items\n", scalar(@all));
printf("part: %d items\n", scalar(@part));
printf("%d items only exists on %s\n", scalar(@part_only), $part_file);
オライリーの Perl クックブック初版第1刷 の 106ページに間違いがある。ちなみに和訳版の話。
4章 配列 レシピ4.7 「一方の配列にはあって他方の配列にはない要素を見つける」の解説で、
以下のようなコードが出てくるが、unless exists でないと正しく動かない。
サンプルコードを array_diff.pl として保存して実行した結果。
4章 配列 レシピ4.7 「一方の配列にはあって他方の配列にはない要素を見つける」の解説で、
以下のようなコードが出てくるが、unless exists でないと正しく動かない。
- 間違ったコード
foreach $item (@A) {
push (@aonly, $item) unless $seen{$item};
$seen{$item} = 1; # 一回出現した要素をマーキングしておく
}
- 正しいコード
foreach $item (@A) {
push (@aonly, $item) unless exists $seen{$item};
$seen{$item} = 1; # 一回出現した要素をマーキングしておく
}
- サンプルコード
#!/usr/bin/perl -w
use strict;
my @A = qw(a b c d e);
my @B = qw(b c e);
my %seen;
my @aonly;
@seen{@B} = ();
foreach my $item (@A) {
push (@aonly, $item) unless exists $seen{$item};
$seen{$item} = 1; # 一回出現した要素をマーキングしておく
}
print join("\n", @aonly);
サンプルコードを array_diff.pl として保存して実行した結果。
$ perl -wl array_diff.pl
a
d
コンストラクタの第二引数にエンコードを指定する。
Jcode.pm 0.83 から抜粋。
Perl 5.8 なら Encode モジュールを使った方が良いらしいけど、私のところではまだ Jcode.pm で十分。場合によっては jcode.pl でもいい。楽に書ければどちらでも OK。
$euc_str = Jcode->new($str, 'utf8')->euc;
Jcode.pm 0.83 から抜粋。
$j = Jcode->new($str [, $icode]);
Creates Jcode object $j from $str. Input code is automatically checked
unless you explicitly set $icode. For available charset, see L<getcode>
below.
- エンコードの一覧
ascii Ascii (Contains no Japanese Code)
binary Binary (Not Text File)
euc EUC-JP
sjis SHIFT_JIS
jis JIS (ISO-2022-JP)
ucs2 UCS2 (Raw Unicode)
utf8 UTF8
Perl 5.8 なら Encode モジュールを使った方が良いらしいけど、私のところではまだ Jcode.pm で十分。場合によっては jcode.pl でもいい。楽に書ければどちらでも OK。
文字列中に URL があった場合はリンクになってほしいが、URL が長すぎるとブラウザで表示したときに横スクロールバーが出たり、レイアウトが崩れたりする。これを防ぐために、大崎さんの perl メモで紹介されていたリンク置換スクリプトに手を加え、指定バイト数を超える URL の場合に省略表示するようにした。
blockquote タグの中ではリンクを張らないようにした。引用文を省略してしまって意味が変わってしまっては困るし、コードを貼り付けたときに余計なところま URL だと判断してしまうから。つまり、
例) $str = "http://www.google.co.jp/";
- その他細かい修正点
メールアドレスの置換は必要ないので削除した。blockquote タグの中ではリンクを張らないようにした。引用文を省略してしまって意味が変わってしまっては困るし、コードを貼り付けたときに余計なところま URL だと判断してしまうから。つまり、
$str = "http://www.google.co.jp/";を変換すると、
$str = "<a href="http://www.google.co.jp/";">http://www.google.co.jp/";</a>になってしまうのだ。ソースを引用するよりも実例を挙げた方が早いかな。
例) $str = "http://www.google.co.jp/";
sub MakeLink() {
# perl メモ 自動で URI(URL) のリンクを張る 参照。
# http://www.din.or.jp/~ohzaki/perl.htm#AutoLink
# $str の中の URI(URL) にリンクを張った $result を作る
my $str = shift;
# URL の長さ制限。この値より長い URLは <a href="元の長い URL">省略されたURL ...</a> となる。
# 0 で無制限。
my $MAX_URL_LENGTH = 72;
my $text_regex = q{[^<]*};
# html タグの正規表現
my $tag_regex_ = q{[^"'<>]*(?:"[^"]*"[^"'<>]*|'[^']*'[^"'<>]*)*(?:>|(?=<)|$(?!\n))}; #'}}}}
my $comment_tag_regex = '<!(?:--[^-]*-(?:[^-]+-)*?-(?:[^>-]*(?:-[^>-]+)*?)??)*(?:>|$(?!\n)|--.*$)';
my $tag_regex = qq{$comment_tag_regex|<$tag_regex_};
# ftp url の正規表現
my $ftp_URL_regex =
q{\bftp://(?:(?:[-_.!~*'()a-zA-Z0-9;&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*} .
q{(?::(?:[-_.!~*'()a-zA-Z0-9;&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*)?@)?(?} .
q{:(?:[a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.)*[a-zA-Z](?:[-a-zA-} .
q{Z0-9]*[a-zA-Z0-9])?\.?|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(?::[0-9]*)?} .
q{(?:/(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*(?:/(?} .
q{:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*)*(?:;type=[} .
q{AIDaid])?)?(?:\?(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]|%[0-9A-Fa-f][0-9} .
q{A-Fa-f])*)?(?:#(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]|%[0-9A-Fa-f][0-9A} .
q{-Fa-f])*)?};
# http url の正規表現
my $http_URL_regex =
q{\b(?:https?|shttp|ms-help)://(?:(?:[-_.!~*'()a-zA-Z0-9;:&=+$,]|%[0-9A-Fa-f} .
q{][0-9A-Fa-f])*@)?(?:(?:[a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.)} .
q{*[a-zA-Z](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?\.?|[0-9]+\.[0-9]+\.[0-9]+\.} .
q{[0-9]+)(?::[0-9]*)?(?:/(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f]} .
q{[0-9A-Fa-f])*(?:;(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-} .
q{Fa-f])*)*(?:/(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f} .
q{])*(?:;(?:[-_.!~*'()a-zA-Z0-9:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*)*)} .
q{*)?(?:\?(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])} .
q{*)?(?:#(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]|%[0-9A-Fa-f][0-9A-Fa-f])*} .
q{)?};
my $result = '';
my $skip = 0;
while ($str =~ /($text_regex)($tag_regex)?/gso) {
last if $1 eq '' and $2 eq '';
my $text_tmp = $1;
my $tag_tmp = $2;
if ($skip) {
$result .= $text_tmp . $tag_tmp;
$skip = 0 if $tag_tmp =~ /^<\/[aA](?![0-9A-Za-z])/;
} else {
$text_tmp =~ s{($http_URL_regex|$ftp_URL_regex)}
{my $org = $1;
(my $tmp = $org) =~ s/"/"/g;
$org = substr($org, 0, $MAX_URL_LENGTH) . ' ...' if ($MAX_URL_LENGTH != 0 && $MAX_URL_LENGTH < length($org));
'<a href="' . "$tmp\">$org</a>"}ego;
$result .= $text_tmp . $tag_tmp;
$skip = 1 if $tag_tmp =~ /^<[aA](?![0-9A-Za-z])/;
if ($tag_tmp =~ /^<(xmp|plaintext|script|blockquote)(?![0-9A-Za-z])/i) {
$str =~ /(.*?(?:<\/$1(?![0-9A-Za-z])$tag_regex_|$))/gsi;
$result .= $1;
}
}
}
return $result;
}
2003-10-29 に date コマンドって超便利というメモを書いたが、perl でも類似のことができることがわかった。
$ perl -MPOSIX -e 'print strftime "%Y-%m-%d %H:%M", localtime;'chalow で使っているやり方だ。
2003-12-10 14:09
http://www.kaimei.org/note/book_out/eff_perl.html
Effective Perl は一時期買おうかと思ってたんだけど、Perl クックブックがあるから見送った。
http://www.ascii.co.jp/bookmart/pdf/47561/4756130577.pdf には pdf もある。
Effective Perl は一時期買おうかと思ってたんだけど、Perl クックブックがあるから見送った。
http://www.ascii.co.jp/bookmart/pdf/47561/4756130577.pdf には pdf もある。
Effective Perl
ジョセフ・N. ホール / ランドル・L. シュワォーツ / Joseph N. Hall / Randal L. Schwartz / 吉川 邦夫
発売日: 1999/03
amazon で詳しく見る bk1で詳しく見る
ジョセフ・N. ホール / ランドル・L. シュワォーツ / Joseph N. Hall / Randal L. Schwartz / 吉川 邦夫
発売日: 1999/03
amazon で詳しく見る bk1で詳しく見る
おさらい。
作成したハッシュへのリファレンスを配列に入れてあげると、ハッシュの配列を作れる。
アクセスは以下のようにする。
アクセス
http://www.kt.rim.or.jp/~kbk/perl5.005/perldsc.html
サンプル付き。おすすめ。
Perl小技集3 ハッシュの配列 配列のハッシュ
http://www.webkoza.com/doc1/perl_m.htm
あとは、Google で perl ハッシュの配列 配列のハッシュ を検索すれば十分だろう。
- ハッシュの配列
ハッシュコンストラクタ {} を使って無名ハッシュを作成し、作成したハッシュへのリファレンスを配列に入れてあげると、ハッシュの配列を作れる。
my @list_of_hash = ();
foreach my $i (1..5) {
my $hash = {
name => 'hiroaki' . $i,
count => $i
};
push(@list_of_hash, $hash);
}
アクセスは以下のようにする。
foreach my $hash (@list_of_hash) {
printf("%s: %d\n", $hash->{name}, $hash->{count});
}
- 配列のハッシュ
# リストを返す関数を呼び出す
for $group ( "simpsons", "jetsons", "flintstones" ) {
$HoL{$group} = [ get_family($group) ];
}
アクセス
$HoL{flintstones}[0] = "Fred";
- 参考
perldsc - Perl のデータ構造クックブックhttp://www.kt.rim.or.jp/~kbk/perl5.005/perldsc.html
サンプル付き。おすすめ。
Perl小技集3 ハッシュの配列 配列のハッシュ
http://www.webkoza.com/doc1/perl_m.htm
あとは、Google で perl ハッシュの配列 配列のハッシュ を検索すれば十分だろう。
・Linux
・Apache
・PostgreSQL
を、
<ul>
<li>Linux</li>
<li>Apache</li>
<li>PostgreSQL</li>
</ul>
に置き換える正規表現。
「引用部分を <blockquote></blockquote> で囲む正規表現」を使えば、文字列中から ・ で始まる部分を抽出できるだろう。あとは、・ から次の ・ の終わりまでを <li></li> で囲めばいい。しかし、どこを終わりと見なせばいいんだろう? 改行までだと、複数行の時に困る。
そうだ、最初の ・ の前と、最後の ・ の後には空行を入れさせるようにすればいいのかな。
でも、複数行のリストって、そんなに使うかなあ? 行末まででもいいような気がしてきた・・・。
・Apache
・PostgreSQL
を、
<ul>
<li>Linux</li>
<li>Apache</li>
<li>PostgreSQL</li>
</ul>
に置き換える正規表現。
「引用部分を <blockquote></blockquote> で囲む正規表現」を使えば、文字列中から ・ で始まる部分を抽出できるだろう。あとは、・ から次の ・ の終わりまでを <li></li> で囲めばいい。しかし、どこを終わりと見なせばいいんだろう? 改行までだと、複数行の時に困る。
そうだ、最初の ・ の前と、最後の ・ の後には空行を入れさせるようにすればいいのかな。
こんな感じ。ちょっと面倒かもしれないけど。そうすれば複数行に渡るリストでも大丈夫だ。
・Linux
・Apache
・PostgreSQL
でも、複数行のリストって、そんなに使うかなあ? 行末まででもいいような気がしてきた・・・。
chalow 0.19 から。
なるほどなるほど。
m オプションを使い、^ を各行に適用させる。
あとは \t で始まり、> か | が存在し、改行で終わる行を + でグループ化する。
それらを <blockquote></blockquote> で囲む。
分岐してるのは、置換後の文字列に > と | を 残すかどうかを分けるため。
引用記号の > と | を残さないようにしてる方は、さらに正規表現で置換してるのか。
ところで、
x は正規表現中にの空白文字を無視するオプション。
これを使うことで、コメントを埋め込んだり、インデントを付けたりできる。
### 引用 (quote): "| ..." か "> ..."
if ($remove_quote_mark == 1) {
my $a;
s!((^\t((\||>)[^\n]*)\n)+)!'<blockquote>'.($a = $1,
$a =~ s{^\t(\||>)\s?}{\t}gm, $a).'</blockquote>'!gme;
} else {
s!((^\t((\||>)[^\n]*)\n)+)!<blockquote>$1</blockquote>!gmx;
}
なるほどなるほど。
m オプションを使い、^ を各行に適用させる。
あとは \t で始まり、> か | が存在し、改行で終わる行を + でグループ化する。
それらを <blockquote></blockquote> で囲む。
分岐してるのは、置換後の文字列に > と | を 残すかどうかを分けるため。
引用記号の > と | を残さないようにしてる方は、さらに正規表現で置換してるのか。
ところで、
s!((^\t((\||>)[^\n]*)\n)+)!<blockquote>$1</blockquote>!gmx;正規表現オプション x は不要だろう。
x は正規表現中にの空白文字を無視するオプション。
これを使うことで、コメントを埋め込んだり、インデントを付けたりできる。
$ perl -lne 's/([^\w ])/"%".unpack("H2", $1)/eg; tr/ /+/; printf("http://www.google.co.jp/search?num=50&lr=lang_ja&ie=euc-jp&q=%s\n", $_)'
こんな風に使う。
$ echo 'Linux 日本語' | perl -lne 's/([^\w ])/"%".unpack("H2", $1)/eg; tr/ /+/; printf("http://www.google.co.jp/search?num=50&lr=lang_ja&ie=euc-jp&q=%s\n", $_)'http://www.google.co.jp/search?num=50&lr=lang_ja&ie= ...
- alias しておこう。
alias googleuri='perl -lne '"'"'s/([^\w ])/"%".unpack("H2", $1)/eg; tr/ /+/; printf("http://www.google.co.jp/search?num=50&lr=lang_ja&ie=euc-jp&q=%s\n", $_)'"'"
としておけば、
$ echo 'Linux 日本語' |googleuriとなる。
http://www.google.co.jp/search?num=50&lr=lang_ja&ie=euc-jp&q=Linux+%c6%fc%cb%dc%b8%ec
いろんなところで使いそうなのでメモ。
大崎さんの perl メモの URIエスケープ・アンエスケープする に詳しい。
http://www.din.or.jp/~ohzaki/perl.htm#JP_Escape
大崎さんはエスケープ対象の指定に [^\w ] を使っている。他のウェブサイトでは s/(\W)/'%' . unpack('H2', $1)/eg; という書き方をしているものがあるが、(\W) では正しい結果にならない。\W ではスペースが %20 にエスケープされてしまうからだ。
まず \w とスペース以外を %HH 形式に変換し、そのあとスペースを + にする、というのが正しい。もっとも、多くのプログラムでは以下に示すようなデコードを行っているので、%20 でも + でもスペースに変換してくれる。
大崎さんの perl メモの URIエスケープ・アンエスケープする に詳しい。
http://www.din.or.jp/~ohzaki/perl.htm#JP_Escape
- url encode
sub url_encode($) {
my $str = shift;
$str =~ s/([^\w ])/'%'.unpack('H2', $1)/eg;
$str =~ tr/ /+/;
return $str;
}
大崎さんはエスケープ対象の指定に [^\w ] を使っている。他のウェブサイトでは s/(\W)/'%' . unpack('H2', $1)/eg; という書き方をしているものがあるが、(\W) では正しい結果にならない。\W ではスペースが %20 にエスケープされてしまうからだ。
application/x-www-form-urlencoded でのエンコードでは
control names と values のスペースは + に変換し,
それ以外の予約文字を %HH の形式に URIエスケープします.
まず \w とスペース以外を %HH 形式に変換し、そのあとスペースを + にする、というのが正しい。もっとも、多くのプログラムでは以下に示すようなデコードを行っているので、%20 でも + でもスペースに変換してくれる。
- url decode
sub url_decode($) {
my $str = shift;
$str =~ tr/+/ /;
$str =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg;
return $str;
}
2003-08-13 で書いた、syslog の使い方について調査。
運用ツール、夜間バッチなどでエラーや作業結果をログに書くとき、
アプリケーション独自のログに書くよりも、syslog に書いた方が汎用的だし、管理もしやすい。
/etc/syslog.conf に、テスト用のログのファシリティと出力先をセット。
コマンドラインから syslog にメッセージを送ってみる。
書けたようだ。
そんな時にどうすれば楽かを調べた。perl から syslog に書くためのモジュールがあるようだ。
Sys::Syslog モジュールを使う時の設定
http://www.egroups.co.jp/message/perl-lesson/722
冗長だと思われる部分を削り、テスト環境固有の情報を追加したのが以下のコード。
これだけ。
なるほど、システムのインクルードファイルを変換する必要があるのだな。
ということで作業。しかし、上記作業をしても、stdarg.ph がない、というエラーが出てしまう。
Google で syslog stdarg.ph を検索したら、見事にヒット。Nomail というツールの FAQ に、stdarg.ph のことが書いてあった。
http://www.ku3g.org/negi/nomail/FAQ
これを参考にして h2ph を実行したらうまくいった。それでも、-w スイッチを付けてスクリプトを実行すると以下のような警告が表示されてしまう。まあ致命的ではないので問題はないだろう。
Re: [perl-lesson] Sys::Syslog モジュールを使う時の設 定
http://www.egroups.co.jp/message/perl-lesson/728
運用ツール、夜間バッチなどでエラーや作業結果をログに書くとき、
アプリケーション独自のログに書くよりも、syslog に書いた方が汎用的だし、管理もしやすい。
- 事前準備とテスト
syslog.conf を設定。/etc/syslog.conf に、テスト用のログのファシリティと出力先をセット。
# syslog test
local6.* /var/log/syslog_test.log
コマンドラインから syslog にメッセージを送ってみる。
$ logger -p local6.debug -i -t "sample" "sample log"
$ tail -f /var/log/syslog_test.log
Aug 20 15:14:20 pro sample[4826]: sample log
書けたようだ。
- perl から syslogd にメッセージを送る
ツールは シェルスクリプトや perl スクリプトで書く場合が多い。そんな時にどうすれば楽かを調べた。perl から syslog に書くためのモジュールがあるようだ。
- perl から syslod に書くためのサンプルスクリプト
perl-lesson ML の過去ログに良さそうなものがあった。Sys::Syslog モジュールを使う時の設定
http://www.egroups.co.jp/message/perl-lesson/722
冗長だと思われる部分を削り、テスト環境固有の情報を追加したのが以下のコード。
#!/usr/bin/perl
use Sys::Syslog; # all except setlogsock, or:
print "Syslog test\n";
openlog(__FILE__, 'cons,pid', 'local6');
syslog('debug', 'this is another test');
closelog();
これだけ。
- 環境
[hiroaki@sonic hiroaki]$ cat /etc/turbolinux-release
Turbolinux Server 6.5 (Jupiter)
[hiroaki@sonic hiroaki]$ perl -v
This is perl, version 5.005_03 built for i386-linux
- ライブラリが足りない。
上記スクリプトを動かす前に -wc のチェックをかけたところ、エラーが発生した。$ perl -wc syslog.pl
Constant subroutine __stub_lgammal redefined at /usr/lib/perl5/5.00503/i386-linux/gnu/stubs.ph line 58.
Constant subroutine __stub_lgammal_r redefined at /usr/lib/perl5/5.00503/i386-linux/gnu/stubs.ph line 64.
Can't locate stdarg.ph in @INC (did you run h2ph?) (@INC contains: /usr/lib/perl5/5.00503/i386-linux /usr/lib/perl5/5.00503 /usr/lib/perl5/site_perl/5.005/i386-linux /usr/lib/perl5/site_perl/5.005 .) at /usr/lib/perl5/5.00503/i386-linux/sys/syslog.ph line 7.
BEGIN failed--compilation aborted at syslog.pl line 3.
- did you run h2ph?
did you run h2ph? というエラーメッセージにあるとおり、h2ph という作業が必要なようだ。h2ph はシステムにあるヘッダを perl から扱えるようにする perl スクリプト。作者は Larry Wall だ。Google で h2ph perl syslog を検索すると、qm-pop3d-pw というツールの readme.txt がヒット。そこの最後の行に記述があった。おまけ
perlでSys::Syslogモジュールを使うのはちょっと面倒です。
うまく設定できていないと、-lオプションを付けると認証できなくなります。
# cd /usr/include
# h2ph *
# cd /usr/include/sys
# h2ph *
を行って、syslog.phを作成しておいてください。
また、Syslog.pmの一部のバージョンではうまく動かないことがありますが、
そのような場合、connect関数内にある、my $syslog = &_PATH_LOGをmy $syslog = &_PATH_LOG()とすると動くことがあります。
なるほど、システムのインクルードファイルを変換する必要があるのだな。
ということで作業。しかし、上記作業をしても、stdarg.ph がない、というエラーが出てしまう。
Google で syslog stdarg.ph を検索したら、見事にヒット。Nomail というツールの FAQ に、stdarg.ph のことが書いてあった。
http://www.ku3g.org/negi/nomail/FAQ
Q. stdarg.ph が無いというエラーが出るんですけど
A. Linux の glibc-2.1.x では syslog.h から stdarg.h が include
されています。stdarg.h は
/usr/lib/gcc-lib/i586-redhat-linux/2.95.3/include/
とか非常にわかりにくいところにあり,Perl をインストールする時
に見落していることが多いようです。
# cd /usr/lib/gcc-lib/i586-redhat-linux/2.95.3/include/
# h2ph stdarg.h
として生成してください。
これを参考にして h2ph を実行したらうまくいった。それでも、-w スイッチを付けてスクリプトを実行すると以下のような警告が表示されてしまう。まあ致命的ではないので問題はないだろう。
$ perl -wc /home/hiroaki/test/syslog.pl
Constant subroutine __stub_lgammal redefined at /usr/lib/perl5/5.00503/i386-linux/gnu/stubs.ph line 58.
Constant subroutine __stub_lgammal_r redefined at /usr/lib/perl5/5.00503/i386-linux/gnu/stubs.ph line 64.
Constant subroutine __need___va_list undefined at /usr/lib/perl5/site_perl/5.005/i386-linux/stdarg.ph line 9.
/home/hiroaki/test/syslog.pl syntax OK
- まだ書けない。
実行してみたが、全然書かれない。tail -f /var/log/syslog_test.log しているが、一行も書き込まれないのだ。Google で perl Sys::Syslog を検索すると、再び perl-lesson ML がヒット。Re: [perl-lesson] Sys::Syslog モジュールを使う時の設 定
http://www.egroups.co.jp/message/perl-lesson/728
> Syslog.pmを覗いてみたところ、UDPでsyslogに書き込んでるみたいですが、
> 僕のRed Hat 6.2に入ってるsysklogd-1.3.31-17は、
> デフォルトではUDP経由での書き込みを受け付けません。
> #man syslogdでrオプションのところに書いてありました。
perldoc Sys::Syslogしてみると、
setlogsock $sock_type (added in 5.004_02)
Sets the socket type to be used for the next call to
openlog() or syslog() and returns TRUE on success,
undef on failure.
A value of 'unix' will connect to the UNIX domain
socket returned by the _PATH_LOG macro…
と書かれているので、「setlogsock 'unix';」とopenlogの前に書いたら
syslogdのオプションを変えなくてもうまく行きそうです。
(「use Sys::Syslog qw(:DEFAULT setlogsock);」とされてますし」
- 書けた。
setlogsock を呼ぶようにしたら書けた。最終的なコードは以下のようになった。#!/usr/bin/perl
# use Sys::Syslog; # all except setlogsock, or:
use Sys::Syslog qw(:DEFAULT setlogsock); # default set, plus setlogsock
print "Syslog test\n";
setlogsock 'unix';
openlog(__FILE__, 'cons,pid', 'local6');
syslog('debug', 'this is another test');
closelog();
http://bulknews.net/lib/doc-ja/etoys.ja.html
やっぱり perl の真髄はモジュールにありということか。例外をサポートしてる言語は便利だな。
やっぱり perl の真髄はモジュールにありということか。例外をサポートしてる言語は便利だな。
セッションを実現するための仕組みについて調査。
ライブラリに任せるなら、Apache::Session が有名なようだ。
Apache::Session モジュール v1.52
http://member.nifty.ne.jp/hippo2000/perltips/apache/Session. ...
Perlでセッション管理するためのPerlモジュール
http://www.ahref.org/perl/a.phtml?number=10000007
Perlモジュール/Apache::Session - Perlによるセッション管理モジュール。
http://digit.que.ne.jp/work/index.cgi?Perl%A5%E2%A5%B8%A5%E5 ...
ライブラリに任せるなら、Apache::Session が有名なようだ。
Apache::Session モジュール v1.52
http://member.nifty.ne.jp/hippo2000/perltips/apache/Session. ...
Perlでセッション管理するためのPerlモジュール
http://www.ahref.org/perl/a.phtml?number=10000007
Perlモジュール/Apache::Session - Perlによるセッション管理モジュール。
http://digit.que.ne.jp/work/index.cgi?Perl%A5%E2%A5%B8%A5%E5 ...
たつをの ChangeLog から。
http://nais.to/~yto/clog/2003-08.html#2003-08-12-1
http://nais.to/~yto/clog/2003-08.html#2003-08-12-1
wget http://openlab.ring.gr.jp/Jcode/Jcode-0.83.tar.gz
tar zxvf Jcode-0.83.tar.gz
cd Jcode-0.83
perl Makefile.PL
make
make test
make install
ログというのは行単位が基本。しかし、一部には複数行にまたがって出力されるものもある。そんなときにどう grep するかを考えた。JM で grep の man を見てみたが、良さそうなオプションは無かった。こんなときは perl で one liner を書くと楽だ。
grep したいファイルが単数の場合
grep したいファイルが複数の場合
デリミタが改行2つなので split() したら改行が消えて行が繋がってしまって残念な思いをするのを回避したい場合。なんか世話が焼きすぎな気もするが、以下のようにさらに \n\n で join() してやればいい。
-0777 で セパレータを無効にしている。すなわちファイル全体が一つになる。
-n と -e だけだと、-e で指定したスクリプトが標準入力から一行読み込まれる度に実行される。一方、-0777 と -n を組み合わせると、とりあえず標準入力から全行を読み込み、$_ に格納し、一度だけスクリプトが実行される、という動きをするようだ。
あとは全体を指定したデリミタで split() して、配列に格納。配列を grep() してマッチした要素だけを取得して print してる。
一度にファイルを読み込んでしまうなど、効率の面ではあまり良くないけど、気にするな。
- 複数行にわたるログの例。
架空のものだけど。Date: 11/Jul/2003:09:05:21 +0900
Protocol: HTTP/1.1
User-Agent: Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.4b)
Status: 200
--------
Date: 11/Jul/2003:09:05:30 +0900
Protocol: HTTP/1.1
User-Agent: Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.4b)
Status: 304
--------
Date: 11/Jul/2003:09:06:00 +0900
Protocol: HTTP/1.1
User-Agent: Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.4b)
Status: 304
- デリミタ(区切り文字)で split する
明確な区切り文字があれば、そこで分割してから grep してやればいい。以下、デリミタが -------- で、linux という文字列が入ってるブロックを grep する例。grep したいファイルが単数の場合
$ perl -0777 -ne 'print grep(/linux/i, split("--------", $_));' log.2003.07.10
grep したいファイルが複数の場合
$ perl -0777 -ne 'print grep(/linux/i, split("--------", $_));' log.2003.07*
- デリミタが改行2つの場合 \n\n
\n\n\ は、http リクエストやレスポンスにおいてヘッダとボディを分ける時にも使われる。$ perl -0777 -ne 'print grep(/linux/i, split("\n\n", $_));' < log.2003.07.10.2lf_delimiter
デリミタが改行2つなので split() したら改行が消えて行が繋がってしまって残念な思いをするのを回避したい場合。なんか世話が焼きすぎな気もするが、以下のようにさらに \n\n で join() してやればいい。
$ perl -0777 -ne 'print join("\n\n", grep(/linux/i, split("\n\n", $_)));' < /cygdrive/n/pfexport.txt
- 解説
-n で全行を標準入力から取得している。-0777 で セパレータを無効にしている。すなわちファイル全体が一つになる。
-n と -e だけだと、-e で指定したスクリプトが標準入力から一行読み込まれる度に実行される。一方、-0777 と -n を組み合わせると、とりあえず標準入力から全行を読み込み、$_ に格納し、一度だけスクリプトが実行される、という動きをするようだ。
$ perl -ne 'print "enjoy perl!"' < /cygdrive/n/pfexport.txtとしたとき、enjoy perl! は行数分 print されたが、
$ perl -0777 -ne 'print "enjoy perl!"' < /cygdrive/n/pfexport.txtだと一度だけしか print されなかった。
あとは全体を指定したデリミタで split() して、配列に格納。配列を grep() してマッチした要素だけを取得して print してる。
一度にファイルを読み込んでしまうなど、効率の面ではあまり良くないけど、気にするな。
# 日付単位に分解
sub split_entry() {
my $str = shift;
return split(/(?=^\d{4}-\d{2}-\d{2})/m, $str);
}
# * で始まるアイテム単位に分解
sub split_item() {
my $str = shift;
$str =~ s/^\t//gm;
my @item = split(/(?=^\*.*:)/m, $str);
shift(@item); # 先頭は日付のところなので削除。
return @item;
}
# アイテムを file_name と body に分解
sub split_body() {
my $str = shift;
# print $str; # DBG
my ($file_name, $body) = ($str =~ /^\*\s+(.*?)\n(.*)/s);
return ($file_name, $body);
}
- one liner、日本語で言うと一行野郎
いわゆる一行スクリプトを解説したページ。昔はよく参照してたが、google をいくら探しても見つからない。
仕方がないので、ここに転載しておく。
- Internet Archive で見つけた。
「perl でいいよ」 というページの一コーナーだったようだ。http://web.archive.org/web/20020214191434/http://www13.cds.n ...
ところで、なんで -a でスプリットされた変数へは @F[n] でアクセスするんだろう?
たまに中身がリファレンスだったりすることがあるし、よくわからん。
-w を付けると、$F[n] にしなさい、と言われるし。
●標準perlだけで使える1行野郎
[置き換え]
すべてのcのファイル中のfooをbarに置き換える。
bakでバックアップ付き
\bは単語の区切り
perl -p -i.bak -e 's/\bfoo\b/bar/g' *.c
それぞれのファイルに含まれる数値を1増やしてセーブします
bakでバックアップ付
perl -i.bak -pe 's/(\d+)/ 1 + $1 /ge' file1 file2 ....
改行コードを0D0A から 0Aに変換します
perl -pe 's/\015\012/\012/g' file
改行コードを0A から 0D0Aに変換します
perl -pe 's/\012/\015\012/g' file
[部分的な取り出し]
スペースで区切られたフィールドの最初から4番目までと6番目を表示
perl -lane 'print "@F[0..4] $F[6]"' file
コロンで区切られたフィールドの最初から4番目までを表示
perl -F: -lane 'print "@F[0..4]\n"' /etc/passwd
STARTとENDに挟まれた部分を表示
perl -ne 'print if /^START$/ .. /^END$/' file
STARTとENDに挟まれた部分以外を表示
perl -ne 'print unless /^START$/ .. /^END$/' file
最初の50行を表示
perl -pe 'exit if $. > 50' file
最初の10行を表示しない
perl -ne 'print unless 1 .. 10' file
15 から 17 行目だけを表示
perl -ne 'print if 15 .. 17' file
各行の80文字以降を捨てる
perl -lne 'print substr($_, 0, 80) = ""' file
各行の最初の10文字を捨てる
perl -lne 'print substr($_, 10) = ""' file
[よりよいgrep]
commentという文字列が含まれている行を表示
perl -ne 'print if /comment/' duptext
commentという文字列が含まれていない行を表示
perl -ne 'print if /comment/' duptext
commentまたはappleという文字列が含まれている行を表示
perl -ne 'print if /comment/ || /apple/' duptext
commentまたはappleという文字列が含まれている行を表示
perl -ne 'print if /comment/ || /apple/' duptext
commentとappleの両方の文字列が含まれている行を表示
perl -ne 'print if /comment/ || /apple/' duptext
commentという文字列が含まれている行とファイル名を表示
findと一緒に使うと便利
perl -ne 'print "$ARGV:$_" if /comment/' file1 file2 file3 ...
intにだけ一致する(printには一致しない)grep
perl -ne 'print if /\bint\b/' test.txt
port0からport9までの10個の単語に一致する
perl -ne 'print if /\bport\d\b/' test.txt
port0からport9999などといった数字が末尾につく単語に一致する
perl -ne 'print if /\bport\d+\b/' test.txt
[フィールド間の演算]
最初のフィールドと最後から2番目のフィールドを合計する
perl -lane 'print $F[0] + $F[-2]'
[ソート]
ファイルを行単位でソートします
perl -e 'print sort <>' file
ファイルをパラグラフ単位でソートします
perl -00 -e 'print sort <>' file
複数ファイルをファイル毎比較してソートしてから1本のファイルとして表示し
ます
perl -0777 -e 'print sort <>' file1 file2
[反転]
あたえられたファイルを行単位でひっくり返します
perl -e 'print reverse <>' file1
ファイルを文字単位ですべてひっくり返します
perl -0777e 'print scalar reverse <>' f1 f2 f3 ...
ファイルをパラグラフ単位でひっくり返します
perl -00 -e 'print reverse <>' file1 file2 file3 ....
1行の中で文字単位でひっくり返します
perl -nle 'print scalar reverse $_' file1 file2 file3 ....
辞書から回文を探します。残念ながら日本語には対応していません
perl -lne 'print if $_ eq reverse' /usr/dict/words
ファイル中に2回以上連続して現れる単語があれば、そのうちの1個を表示しま
す
perl -0777 -ne 'print "$.: doubled $1\n" while /\b(\w+)\b\s+\b\1\b/gi'
[数値変換]
入力された10進数を16進数に変換して表示します。CTRL-Cで終了
perl -ne 'printf "%x\n",$_'
入力された10進数を8進数に変換して表示します。CTRL-Cで終了
perl -ne 'printf "%o\n",$_'
入力された16進数を10進数に変換して表示します。CTRL-Cで終了
perl -ne 'print hex($_)."\n"'
入力された8進数を10進数に変換して表示します。CTRL-Cで終了
perl -ne 'print oct($_)."\n"'
簡易電卓として計算できます。CTRL-Cで終了
perl -ne 'print eval($_)."\n"'
●-Mは反則だろう。でも便利。モジュールな1行野郎
http://www.yahoo.co.jp/のページのソースを表示
perl -MLWP::UserAgent -e 'LWP::UserAgent->new->request(HTTP::Request->new('GET',$ARGV[0]),sub{print $_[0];});' http://www.yahoo.co.jp/
引数で指定したところからすべてのファイルを見つけて表示します
perl -MFile::Find -le 'find {wanted=>sub {print;}},$ARGV[0]'
カレントディレクトリからすべての拡張子plファイルを見つけて表示します
perl -MFile::Find -le 'find {wanted=>sub {print if /\.pl$/;}},$ARGV[0]'
ディレクトリを作ります。mkdirと違って深い階層を一度に作ることができま
す。
perl -MFile::Path -e 'mkpath([@ARGV],1,0777)' test/tmp1 test2/tmp2
Base64でエンコードします
perl -MMIME::Base64 -ne 'print &MIME::Base64::encode_base64( $_)' file
Base64でエンコードされたfileをデコードします
perl -MMIME::Base64 -ne 'print &MIME::Base64::decode_base64( $_)' file
Quoted-Printable でエンコードします(参照 RFC 2045 )
perl -MMIME::QuotedPrint -ne 'print &MIME::QuotedPrint::encode_qp( $_)' file
指定されたページを取ってきて表示します。
perl -MLWP::Simple -e 'getprint "http://www.foo.bar.com/";
ftpでファイルをダウンロードします。
perl -MLWP::Simple -e 'getstore "ftp://ftp.sunet.se/pub/lang/perl/CPAN/src/latest.tar.gz","perl.tar.gz"'
httpドキュメントのミラーリング
perl -MLWP::Simple -e 'mirror("http://www.perl.com/", "perl.html");'
●一行野郎のためのオプション解説
-0数字(8 進数)
レコードセパレータ ($/) を 8 進数で示します。デフォルトセパレーターは改
行です
-00
パラグラフモードにします。パラグラフとは連続した改行で区切られる
文の固まりです
-0777 セパレーターを無効にします。すなわちファイル全体が1パラグラフにな
ります
-a 自動splitモード
配列 @F = split(' ');
区切り文字は-Fで指定可能
デフォルトは1文字スペース
-e
1行野郎のキーワード
コマンドラインにあたえられた文字列をperlのプログラムとして実行します
複数の-eを使うこともできますがセミコロンが必要
-i拡張子
.bakなどを指定することで、個々のファイルのバックアップを取って上書きし
ます。
-l数字(8 進数)
自動chomp
出力時に数字(8 進数)を改行コードに置き換える。
-n
自動ループ
while (<>) {
... # ここでスクリプトが実行される
}
-p
自動ループprint付
while (<>) {
... # ここでスクリプトが実行される
}
組み合わせ
-na -F正規表現
while (<>) {
@F=split(/正規表現/);
... # ここでスクリプトが実行される
}
-pa -F正規表現
while (<>) {
@F=split(/正規表現/);
... # ここでスクリプトが実行される
print ;
}
- IBM developerWorks の Perl One liner 特集
洗練されたPerl: ワンライナー101 と ワンライナー102http://www-6.ibm.com/jp/developerworks/linux/010706/j_l-p101 ...
http://www-6.ibm.com/jp/developerworks/linux/030523/j_l-p102 ...
- 速習 Perl: 1行スクリプト
http://infosys.gsid.nagoya-u.ac.jp/~ohna/perl_lesson/intro2p ...タブやカンマ区切りのテキストの、任意の場所を表示する Perl One liner。
-a デリミタで split を実行し、配列 F に格納する。
-n 自動ループ。以下を参照。
- デリミタがカンマで n = 5のとき
$ perl -F, -lane 'print "$F[0..4]"' data.csv
- オプションの説明:
-F デリミタの指定。デフォルトはスペース。-F, でカンマをデリミタにする。-a デリミタで split を実行し、配列 F に格納する。
-n 自動ループ。以下を参照。
while (<>) {
... # ここでスクリプトが実行される
}
Unicode 関連モジュールやドキュメントがある。
http://homepage1.nifty.com/nomenclator/perl/
http://homepage1.nifty.com/nomenclator/perl/
テストデータを格納したテキストから SQL の INSERT 文を作るため、
一行スクリプトを書いた。しかし、意図した動作をしてくれない。
シェルが -e の中身を渡すときのデリミタとしてシングルクオートを使っているため、
うまくパースされないのであろう。
というか、うまくパースされて無かったことにされているのだと思う。
2ch.net の Perlについての質問箱 5箱目 に聞いてみたが、結局自分で解答を見つけた。
http://pc2.2ch.net/test/read.cgi/tech/1053053082/149-160
$ perl -n -l -e 'printf("INSERT INTO uniq_uri (uri) VALUES (\x27%s\x27);\n", $_);' 5digit.txt
一行スクリプトを書いた。しかし、意図した動作をしてくれない。
$ perl -n -l -e 'printf("INSERT INTO uniq_uri (uri) VALUES ('%s');\n", $_);' 5digit.txtとやってもシングルクオートが出力されない。
シェルが -e の中身を渡すときのデリミタとしてシングルクオートを使っているため、
うまくパースされないのであろう。
というか、うまくパースされて無かったことにされているのだと思う。
2ch.net の Perlについての質問箱 5箱目 に聞いてみたが、結局自分で解答を見つけた。
http://pc2.2ch.net/test/read.cgi/tech/1053053082/149-160
- 解答
アスキーコードでシングルクオートを指定してやればいい。コードの見通しが悪くなるけど。$ perl -n -l -e 'printf("INSERT INTO uniq_uri (uri) VALUES (\x27%s\x27);\n", $_);' 5digit.txt
Perl Tutorial: Start (日本語訳)
http://web.archive.org/web/20030421165224/http://plaza27.mbn ...
原文は http で agora.leeds.ac.uk/Perl/ にあるらしいけど、DNS 正引きできない。
http://web.archive.org/web/20030421165224/http://plaza27.mbn ...
原文は http で agora.leeds.ac.uk/Perl/ にあるらしいけど、DNS 正引きできない。
- 2003-04-18 と 2003-04-22 から続く整形スクリプト。
- 今までとは別の one liner を考えた。今回の方が表記自体はスマートだと思う。
(?!PATTERN) は、「否定の先読み」を表す。$ perl -p -i.bak -e 's/^((?!\t)(?!\d{4}-\d{2}-\d{2}\s+.*?<.*>).+)/\t$1/; s/\s+$/\n/;' /home/hiroaki/log.txt
- でも性能が悪いみたい。
$ time perl -p -i.bak -e 's/^((?!\t)(?!\d{4}\-\d{2}\-\d{2}\s+.*?<.*>).+)/\t$1/; s/\s+$/\n/;' /home/hiroaki/log.txt
real 0m0.567s
user 0m0.470s
sys 0m0.080s
うーん、複雑な正規表現はコスト高いのかなあ? そんなに複雑でもないと思うけど・・・。
- 毎回タブ付けてキレイにインデントしながらメモを取るなんてかったるいことやってられねーよ、
ってことで、perl one liner。というか単にふつーに書いたスクリプトを無理矢理 one liner にしただけだったりする。
$ perl -p -i.bak -e 'if ($_ =~ /^[^\t]/) { if ($_ !~ /^\d{4}-\d{2}-\d{2}\s+.*?<.*>$/) {$_ = "\t" . $_;} $_ =~ s/\s+$/\n/;}' /home/hiroaki/log.txt
http://www.geocities.co.jp/SiliconValley-Oakland/1680/xperl/
http://www.geocities.co.jp/SiliconValley-Oakland/1680/xperl/ ...
- perl オブジェクト指向についての文書がある。記述が詳細でわかりやすい。
とくに、継承についての記述が役に立つ。http://www.geocities.co.jp/SiliconValley-Oakland/1680/xperl/ ...
- クロージャ (closure) の利点と使い方についての文書もある。
http://www.geocities.co.jp/SiliconValley-Oakland/1680/xperl/ ...クロージャを一言で説明すると、「実行する関数とアクセス可能な局所変数をまとめたオブジェクト」
- 先頭にタブが入っていない場合で、日付の行ではなかった場合にタブを付加する。
#!/usr/bin/perl
use strict;
defined($ARGV[0]) or die __FILE__ . ": target file not specified\n";
my $memo_file = $ARGV[0];
my @line = &ReadFile($memo_file);
my $str = '';
foreach my $buf (@line) {
if ($buf =~ /^[^\t]/) {
if ($buf !~ /^\d{4}-\d{2}-\d{2}\s+.*?<.*>$/) {
$buf = "\t" . $buf;
}
}
$buf =~ s/\s+$/\n/;
$str.= $buf;
}
&WriteFile(1, $str, $memo_file);
# 引数で指定されたファイルを読み込み、呼び出し側のコンテキストに応じた形で返す
sub ReadFile($) {
my $target_file = $_[0];
my @lines = ();
if (! -e $target_file) {
die(__FILE__ . ": error: ReadFile(): $target_file not found.");
}
if (! -r $target_file) {
die(__FILE__ . ": error: ReadFile(): $target_file: permission denied.");
}
open(FILE, $target_file) or die(__FILE__ . ": error: ReadFile(): can not open $target_file.");
flock(FILE, 1);
@lines = <FILE>;
close(FILE);
return (wantarray ? @lines : join('', @lines));
}
# テキストデータをファイルに書き込む
# 引数1 : 0のとき追記、1のとき上書き
# 引数2 : データ (スカラー)
# 引数3 : ファイル名
# 返り値 : 書き込み成功のとき 1
sub WriteFile($$$) {
my ($overwrite, $data, $target_file) = @_;
if (-e $target_file) {
if (! -w $target_file) {
die(__FILE__ . ": error: WriteFile(): $target_file: permission denied.");
}
}
my $err_msg = __FILE__ . ": error: WriteFile(): Can not open $target_file.";
if ($overwrite) {
open(FILE, ">$target_file") or die($err_msg);
} else {
open(FILE, ">>$target_file") or die($err_msg);
}
flock(FILE, 2);
print(FILE $data);
close(FILE);
return 1;
}
- 2003-04-09 に引き続きちょっとだけ改良。
>YYYY-MM-DD または YYYY-MM-DD の ChangeLog という指定をすると、<a href="#YYYY-MM-DD" title="YYYY年MM月DD日の ChangeLog">YYYY-MM-DD の ChangeLog</a>
に置き換えるようにした。追加したコードは以下の通り。
s!(((\d{4})-(\d{2})-(\d{2}))\s*の\s*changelog)!<a href="#$2" title="$3年$4月$5日の ChangeLog">$1</a>!ig;
s!(>>((\d{4})-(\d{2})-(\d{2})))!<a href="#$2" title="$3年$4月$5日の ChangeLog">$1</a>!ig;
仕事で md5 で生成したハッシュを使うかもしれないので、perl でサンプルコードを書くことにした。
md5sum を計算してくれるモジュールは標準ではインストールされていない。Digest::MD5 が良さそうなので、cpan から Digest-MD5-2.24 をダウンロードしてインストール。このモジュールを選んだのは、Perlの小技 http://member.nifty.ne.jp/hippo2000/perltips/ に紹介記事があったから。
Makefile.pl を実行し、make、su, make install でインストール完了。
ほほう、16進文字列32文字で表記する方法の他にも、base64 でエンコードした文字列 22 文字で表現する方法もあるのか。
md5sum を計算してくれるモジュールは標準ではインストールされていない。Digest::MD5 が良さそうなので、cpan から Digest-MD5-2.24 をダウンロードしてインストール。このモジュールを選んだのは、Perlの小技 http://member.nifty.ne.jp/hippo2000/perltips/ に紹介記事があったから。
Makefile.pl を実行し、make、su, make install でインストール完了。
ほほう、16進文字列32文字で表記する方法の他にも、base64 でエンコードした文字列 22 文字で表現する方法もあるのか。
- 2002-09-30 のスクリプトは、. が全く存在しない文字列を渡したときに、
Use of uninitialized value のエラーになる。以下のようにするのが良いだろう。ていうか要求仕様があいまいだなあ。
・拡張子が無いとき ex) access_log
・拡張子が複数ある時 ex) aceess_log.tar.gz
・拡張子しかないとき ex) .htaccess
上記の時に、どんな文字列を返して欲しいのかを決めないと作りようがない。
# ファイル名から拡張子を除いた部分の文字列を返す
sub get_base_name($) {
my $file_name = shift;
my $basename = $file_name;
$basename =~ s/(.*)\..*?$/$1/;
if ($basename eq '') {
$basename = $file_name;
}
return $basename;
}
自分で作った perl モジュールは、どこにインストールするのが適切なのか。
まず、$ perl -e 'foreach $path (@INC) { print "$path\n"; }' で @INC を確認。
/usr/lib/perl5/5.00503/i386-linux
/usr/lib/perl5/5.00503
/usr/lib/perl5/site_perl/5.005/i386-linux
/usr/lib/perl5/site_perl/5.005
site_perl が標準以外のパッケージがインストールされるディレクトリ。
今回は、aoencode.pm を /usr/lib/perl5/site_perl/5.005 にインストールすることにした。
use aoencode;
まず、$ perl -e 'foreach $path (@INC) { print "$path\n"; }' で @INC を確認。
/usr/lib/perl5/5.00503/i386-linux
/usr/lib/perl5/5.00503
/usr/lib/perl5/site_perl/5.005/i386-linux
/usr/lib/perl5/site_perl/5.005
site_perl が標準以外のパッケージがインストールされるディレクトリ。
今回は、aoencode.pm を /usr/lib/perl5/site_perl/5.005 にインストールすることにした。
- 他にも、use lib プラグマを使って、use する側でライブラリパスを追加する方法もある
use lib '/home/hiroaki/script';use aoencode;
パターンマッチのオプションをちょっとおさらい。
試した環境は TurboLinux 6.5 Server の perl version 5.005_03 built for i386-linux
perldoc perlre も参考になる。
かつ正規表現中に ^ や $ を使って先頭や末尾を指定しているとき。
それ以外の時は意味がないと思われる。
以下の例では、m を付けるとマッチ、付けないとアンマッチ。
普段は . は改行文字にはマッチしないが、s オプションによりマッチするようになる。
改行文字とは \n の事のようだ。\r は オプションの有無にかかわらずマッチした。
以下の例では、s を付けるとマッチ、付けないとアンマッチ。
s オプションにより、. は\n にマッチする。
以下のようなときは、m と s 両方のオプションを指定したときだけマッチする。
pattern という文字が続く。ただし、pattern は行の先頭でなければならないので、
pattern の直前は改行文字。」にマッチ。
こんなマッチをさせたい事って、あんまりないかな。
試した環境は TurboLinux 6.5 Server の perl version 5.005_03 built for i386-linux
perldoc perlre も参考になる。
- m オプション: $str を複数行として扱う。
m オプションを必要とするのは、文字列中に改行文字を含み、かつ正規表現中に ^ や $ を使って先頭や末尾を指定しているとき。
それ以外の時は意味がないと思われる。
以下の例では、m を付けるとマッチ、付けないとアンマッチ。
$str =~ "string\npattern\nptt\nend";
$str =~ /^pattern/m;
- s オプション: 文字列を単数行として扱う。・・・と書くとわかりにくいかな。
. を改行文字にもマッチさせるようになる。普段は . は改行文字にはマッチしないが、s オプションによりマッチするようになる。
改行文字とは \n の事のようだ。\r は オプションの有無にかかわらずマッチした。
以下の例では、s を付けるとマッチ、付けないとアンマッチ。
$str =~ "string\npattern\nptt\nend";
$str =~ /ring.*pattern/s;
- m オプションと s オプションを両方指定すると・・・?
m オプションにより、^ や $ の指定が有効になる。s オプションにより、. は\n にマッチする。
以下のようなときは、m と s 両方のオプションを指定したときだけマッチする。
$str =~ "string\npattern\nptt\nend";「ing という 文字列の後に、0個以上の文字列(改行も文字として扱う)が続き、
$str =~ /ing.*^pattern/ms
pattern という文字が続く。ただし、pattern は行の先頭でなければならないので、
pattern の直前は改行文字。」にマッチ。
こんなマッチをさせたい事って、あんまりないかな。
- Perl での OOP を扱ったメールマガジン。
http://www.melma.com/mag/85/m00014785/a00000001.html- fnumber を使ってカラム名を添字に変換する
print $result->getvalue(0, $result->fnumber('serialnum'));
- 無名ハッシュの配列にマッピングする。
my @sql_result = get_sql_result($result);
print $sql_result[0]->{'ao_cd'};
# SQL の実行結果を結果をハッシュの配列にして返す
sub get_sql_result($) {
my $result = $_[0];
my $record_nums = $result->ntuples;
my $field_nums = $result->nfields;
my @result_records = ();
for (my $i = 0; $i < $record_nums; $i++) {
my $buf = {};
for (my $j = 0; $j < $field_nums; $j++) {
my $field_name = $result->fname($j);
$buf->{"$field_name"} = $result->getvalue($i, $j);
}
push(@result_records, $buf);
}
return @result_records;
}
おさらい。my は ブレース { } で囲まれたブロックに、変数のスコープを限定する。ただし、ブロック内で呼び出したサブルーチンはスコープ外。ブロック内で呼び出したサブルーチン内もスコープに入れたければ、local を使う。
以下のコードを実行すると、スコープの外なので何も表示されない。
use strict; すると、$str の所属パッケージ名が必要、というエラーが出る。
以下のコードを実行すると、スコープの外なので何も表示されない。
use strict; すると、$str の所属パッケージ名が必要、というエラーが出る。
#!/usr/bin/perl
&test_func;
sub test_func {
if (1) {
my $str = 'Hello World';
}
print $str;
}
perl で HTML エスケープする方法。大崎さんのperlメモから。
Namazu でインデキシングする HTML の場合、< > & ” “ もエスケープすること。
Namazu が奇妙な変換をするため。http://www.namazu.org/ml/namazu-users-ja/msg02528.html 参照。
Namazu でインデキシングする HTML の場合、< > & ” “ もエスケープすること。
Namazu が奇妙な変換をするため。http://www.namazu.org/ml/namazu-users-ja/msg02528.html 参照。
# HTML エスケープする
sub HtmlEscape {
my $content = $_[0];
my $eucpre = qr{(?<!\x8F)};
my $eucpost = qr{
(?=
(?:[\xA1-\xFE][\xA1-\xFE])* # JIS X 0208 が 0文字以上続いて
(?:[\x00-\x7F\x8E\x8F]|\z) # ASCII, SS2, SS3 または終端
)
}x;
my @escape_from = qw(& > < " ');
my @escape_to = ('&', '>', '<', '"', ''');
for (my $i = 0; $i <= $#escape_from; $i++) {
$content =~ s/$eucpre\Q$escape_from[$i]\E$eucpost/$escape_to[$i]/g;
}
return $content;
}
マッチした部分をリストコンテキストで受ける。
入れ子なしの単純な html タグに囲まれた文字列を抜き出したいときとかに便利。
入れ子なしの単純な html タグに囲まれた文字列を抜き出したいときとかに便利。
# 本文から小見出しとパラグラフを分離して返す
# 引数 : 本文
# 返り値: ハッシュ
sub SplitBody {
my $body = $_[0];
my %splitted_body;
my @header = ($body =~ m|<!-- MARKER_HEADER -->(.*?)<!-- MARKER_HEADER -->|isg);
my @paragraph = ($body =~ m|<!-- MARKER_BODY -->(.*?)<!-- MARKER_BODY -->|isg);
$splitted_body{'header'} = \@header;
$splitted_body{'paragraph'} = \@paragraph;
return %splitted_body;
}