hollyさんのwiki

システムのバックエンドはなんだかんだとperlをメインでつかってていろいろノウハウがあるのでまとめてみる

インストール

windowsはとりあえずおいといて、linuxだとrpmで入っているバージョンは古いので、ソースから最新のバージョンをインストールする

インストール方法は結構ふるいので、perlbrewとかつかったほうがいい。最近のperl事情2012参照

cd /usr/local/src
wget //www.cpan.org/src/5.0/perl-5.12.2.tar.gz
tar xvfz perl-5.12.2.tar.gz
cd perl-5.12.2
./Configure -Dprefix=/usr/local -Dusethreads -Accflags=-DPERL_REENTRANT_MAXSIZE=65536
make
make test
make install
過去に5.10を入れたのをhttp://blog.livedoor.jp/kurt0027/archives/52014712...に書いているのでそれも参考にする。configureでlibperl.soをどうするかと聞かれたら、とりあえずはyにしておいた

よく使うモジュール

※ぼちぼち更新しないとなあ。情報が古い。おなじく最近の最近のperl事情2012参照
  • Class::Accessor
  • Class::Data::Inheritable
  • Class::Data::Accessor
  • Class::Trigger
  • DBD::Mock
  • Devel::NYTprof
  • FindBin::Real
  • feature ":5.10.0"
  • IPC::Cmd
  • IPC::Run(こっちは標準じゃないよ)
  • IPC::Open2
  • IPE::Open3
  • Path::Class
  • Proc::PID::File
  • Parallel::ForkManager
  • Term::Prompt
  • YAML::Syck
  • Sub::Install
  • Class::C3(NEXTのかわりね) 5.10.0からはmro "c3"とするといいみたい。でその架け橋となるのがMRO::Compat
  • UNIVERSAL::require
  • Module::Pluggable
  • Log::Dispatch::Config
  • autodie
  • parent(use baseの代替。いまや常識のようだ)
  • Sub::Retry

perlをコマンドラインから使う

結構いろいろとオプションがあって、コマンドをいろいろ組み合わせてやるよりも便利かも。例えば
perl -n -a -F: -e 'printf "%-10s %s\n",@F' /etc/shadow
で/etc/shadowからアカウントとパスワードを取り出す。オプションは以下
  • -n はSTDIN(標準入力)、もしくはファイル名から受け取ったデータをwhileでまわす処理を自動化
  • -a autosplit mode。用は区切り文字がある場合に-Fで指定した文字列を@Fという配列にいれてくれる
  • -F -aを指定した場合の区切り文字を設定。/etc/shadowは:で区切られているので今回は-F:で指定。-F//なんかで正規表現を指定することも可能
  • -e eval mode。perlコードを評価する。-eのあとにスクリプトを記述。
  • -i ファイル内部を置換して、バックアップファイルを作成するときに指定
  • -l 出力時に自動改行
  • -E from 5.10。use feature したのとほぼ同じ状態になるらしい。sayが使えたりとか

ファイル一覧と各ファイルサイズを取得したい場合(こんなケースはたまにあったりする)

find /path/to/foo -type f | perl -n -l -e 'printf "%-20s%dbyte\n", $_, -s $_'

さらに上のfind + perlの例でファイルサイズの合計を求める

find /path/to/foo -type f | perl -n -l -e 'BEGIN{ $sum = 0 } $sum += -s; END{ print $sum }'

ファイル内のコンテンツを一括置換してバックアップファイルを作成する †

たとえばhtmlファイル内の<!-- COMMENT -->を削除したいけど1000個以上ファイルがある場合
perl -i.bak -p -e 's/<!\-\- COMMENT \-\->//' *.html
とすればコンテンツを一括で置換してさらにバックアップ(この場合だと拡張子.bakのファイルが作られる)もしてくれる*1。さらに応用でとあるディレクトリ内(ここでは/path/to/cgi-binとする)のファイル内にかかれている/usr/bin/perlを /usr/local/bin/perl とかにする場合は
grep -lr '#!/usr/bin/perl' /path/to/cgi-bin/* | xargs -t perl -i.bak -ple 's|#!/usr/bin/perl|#!/usr/local/bin/perl|g'
とすると一括で変更可能。ただしもとのファイルのバックアップができるのでいらない場合は
find /path/to/cgi-bin -name "*.bak" | xargs rm -f
とかするとOK。

ランダムな文字列を生成したい

uuidgenでもいいんだけど数値主体な文字列になってしまうので
# 長ったらしいから関数化してしまうとよい
randstr() {
    len=$1
    perl -E 'BEGIN{ @chars = ("a".."z","A".."Z",0..9); $len = shift } { @rand_chars = map { $chars[sprintf("%d", rand(scalar(@chars)))] } 1..$len  } END{ say join("", @rand_chars) }' $len
}
使い方は
randstr byte数
randstr 8
kTL4IMT7
こんな感じ

とあるプロセスIDが生きているか知りたい

# no such process PID などのエラーメッセージが返って来なければ生存している
kill 0 $PID
しかし環境によっては使えなかったりするので
perl -e 'print kill 0 => $PID'
で1が返ってきた場合は生存している、0もしくはundefの場合は生存していない。こっちのが確実かも

ファイルの文字コードを調べる

nkfが入っている場合は
# -g は --guessでもよい
nkf -g filename
で調べることができるけど、nkfが古かったり(-gが使えない)nkf自体が入っていない場合はperlのEncode::Guessモジュールを使うことで判別可能
perl -MEncode::Guess -nle 'BEGIN{ @alldata } data.= $_; END{ print guess_encoding($data, qw(euc-jp shiftjis 7bit-jis) )->name }' filename
Encode::Guess::guess_encodingはたまに文字コード判定に失敗する場合がある。失敗した場合は
Can't locate object method "name" via package "shiftjis or euc-jp" (perhaps you forgot to load "shiftjis or euc-jp"?) at -e line 1, <> line 19.
END failed--call queue aborted, <> line 19.
"shiftjis or euc-jp"という文字列で返ってきていることになる。標準モジュールじゃないけど、Jcodeモジュール使う手もあり

プロセスグループの取得/設定

getpgrp, setpgrpを使う。
kill -KILL -`perl -e 'getpgrp($PID)'`
とすると対象プロセスグループに属するプロセス全てにシグナルを送信することができる。 setpgrpは対象プロセスのプロセスグループを変更する時に使う。第1引数が0の場合は現在のプロセスに対して、プロセスグループの設定を行う。
setpgrp 0, $$;
みたいな感じ。詳細はhttp://perl.misty.ne.jp/function02/setpgrp.html

pmsetup

最近ではExUtils::MakeMakerではなく、Module::InstallなるモジュールでCPANにupする時のモジュール雛形をつくるのが流行らしい。それを簡単にしてくれるスクリプトがpmsetup。http://svn.bulknews.net/repos/public/misc/pmsetupからとってくる

使い方は簡単で
# PATH通ってるところにおいて、実行権限つける
pmsetup Module::Name
とするとauthor, emailを聞かれるので適当に答える*3と実行したディレクトリにModule-Nameというディレクトリができるので、後は実際にupするモジュールの入れ替え、Makefile.PLの書き換えを行う。あとは
perl Makefile.PL
make
make test
make manifest
make disttest
make dist
make distclean
とおなじみの作業でtarballが作成される

Data::Dumper

referenceの中身を見たいときなどで、デバッグする時によくお世話になるモジュール

設定しておくと便利な変数

$Data::Dumper::Sortkeys;
1を指定するとハッシュのキーをソートする
$Data::Dumper::Indent
1を指定するとインデントを縮める
$Data::Dumper::Terse
1を指定すると$VAR数字が無くなる
$Data::Dumper::Deparse
1を指定すると# sub { "DUMMY" } をみれるようにする

実際の使い方

local $Data::Dumper::Sortkeys = 1;
local $Data::Dumper::Indent   = 1; 
local $Data::Dumper::Terse    = 1;
local $Data::Dumper::Deparse  = 1;
こんな感じで使う

utf8フラグつきのリファレンスをダンプする場合

http://blogs.yahoo.co.jp/kipp/46446122.html などにも書かれているけど、YAML::Dumpを使うほうがいいかも。
それかUnicode::RecursiveDowngradeを通してからData::Dumperでダンプする

cpanp

昔は
perl -MCPAN -e shell
とかしてたような気がするけど今はこっちがメジャーらしい

インストール

cpanp i module

モジュールのバージョン一覧

cpanp f module
App::CLI::Extensionだと現段階ではこのように表示される
cpanp f App::CLI::Extension
   1 App-CLI-Extension                          2008-09-14   0.01     HOLLY
   2 App-CLI-Extension                          2008-09-14   0.01     HOLLY
   3 App-CLI-Extension                          2009-09-08   0.10     HOLLY
   4 App-CLI-Extension                          2009-09-08   0.10     HOLLY
   5 App-CLI-Extension                          2009-09-08   0.20     HOLLY
   6 App-CLI-Extension                          2009-09-10   0.20     HOLLY
   7 App-CLI-Extension                          2009-09-18   0.30     HOLLY
   8 App-CLI-Extension                          2009-09-08   0.30     HOLLY
   9 App-CLI-Extension                          2009-09-18   0.40     HOLLY
  10 App-CLI-Extension                          2009-09-08   0.40     HOLLY
  11 App-CLI-Extension                          2009-11-29   0.50     HOLLY
  12 App-CLI-Extension                          2009-11-29   0.50     HOLLY
  13 App-CLI-Extension                          2009-11-30   0.60     HOLLY
  14 App-CLI-Extension                          2009-11-30   0.60     HOLLY
  15 App-CLI-Extension                          2009-12-05   0.70     HOLLY
  16 App-CLI-Extension                          2009-12-05   0.70     HOLLY
一覧を参照した上で番号を指定するとその番号に該当するバージョンのモジュールをインストールすることができる
i 1

アンインストール

これが非常によい
cpanp u module

設定一覧

cpanp s conf                                                                           [~/perl]
    allow_build_interactivity '1'
    base                      '/root/.cpanplus'
    buildflags                ''
    cpantest                  ''
    cpantest_mx               ''
    debug                     ''
    dist_type                 ''
    email                     'cpanplus@example.com'
    extractdir                ''
    fetchdir                  ''
    flush                     '1'
    force                     ''
    lib                       []
    makeflags                 ''
    makemakerflags            ''
    md5                       '1'
    no_update                 ''
    passive                   '1'
    prefer_bin                ''
    prefer_makefile           '1'
    prereqs                   '2'
    shell                     'CPANPLUS::Shell::Default'
    show_startup_tip          '1'
    signature                 ''
    skiptest                  ''
    storable                  '1'
    timeout                   '300'
    verbose                   ''
    write_install_logs        '1'

設定変更

cpanpで対話モードにはいったあと
s conf <key> <value>
と入力。モジュールインストール時に依存するモジュールも問い合わせなしでインストールする設定の場合だと
s conf prereqs 1
となる

自分自身のhostname取得

chomp(my $hostname = readpipe "hostname");
でもいいけど
use Sys::Hostname;
my $hostname = hostname;
のが外部コマンド使わんでいい

ip_address <-> hostname

よくある処理だけど、Socketモジュール使うと簡単にできる

ip_address -> hostname

use Socket;

my $ip_address = "xxx.xxx.xxx.xxx";
$hostname = gethostbyaddr(inet_aton($ip_address), AF_INET);
print "$hostname\n";

hostname -> ip_address

use Socket;

my $hostname = "hostname.domain";
my $ip_address = inet_ntoa(inet_aton($hostname));
print"$ip_address\n";

ファイルのhash値とりたい

md5sumを使えば
# md5sum hoge.txt とした場合
413eda89cba6c7c42003091cfe2c78bd  hoge.txt
とすればできるけど、perlだけで実現したい場合は
#!/usr/bin/perl

use strict;
use Digest::MD5;

my $file = shift;

open my $fh, "<", $file or die $!;
my $hash = Digest::MD5->new->addfile($fh)->hexdigest;
close $fh;
print "$hash  $file\n";
で同じような処理が可能。外部コマンドあんまし使いたくない場合はいい。

htpasswdの-mのパスワードを扱う

htpasswdの-mオプションはForce MD5 encryption of the passwordだが、これをperlから扱おうと思っても残念ながら簡単にできない
htpasswd -mbc .htpasswd hoge hogehoge
と実行すると.htpasswdには
hoge:$apr1$EYfANhrC$r2yAPD0VCf0bwgJ54ONPu0
のように保存される。この文字列を扱うにはCrypt::PasswdMD5を使えばよい。.htpasswdに登録されているhogeアカウントのパスワードを照合したい場合は
#!/usr/bin/perl

use strict;
use Crypt::PasswordMD5;

my $password = "hogehoge";
my $md5_crypt_password = '$apr1$EYfANhrC$r2yAPD0VCf0bwgJ54ONPu0';
my($salt) = $md5_crypt_password =~ /^$apr1$(.*{8}).+$/;
if($md5_crypt_password eq apache_md5_crypt($password, $salt)) {
    print "password match\n";
} else {
    print "password unmatch\n";
}
とすればよい

SMTP認証のパスワード生成

PLAIN

  • アカウント holly
  • パスワード hoge
perl -MMIME::Base64 -le 'print encode_base64("$ARGV[0]\0$ARGV[0]\0$ARGV[1]")' holly hoge
amVycnkAamVycnkAcm9jaw
と出力されるので、telnetなどでSMTP認証するときに
# 250 DSNが出力されたあとに↓を入力
auth plain amVycnkAamVycnkAcm9jaw==
# 成功した場合は↓が返ってくる
235 2.0.0 Authentication successful

CRAM-MD5

250 DSNが出力後に、
# ↓を入力
auth cram-md5
# ↓のキーとなる文字列が返ってくるので
334 PDQwMTQ2OTQ3NjQuMTE0OTUxQGZlZG9yYTguZW5kbGVzcy1uYW1lbGVzcy54eHg+
キーとなる文字列が返ってくるので
# /usr/bin/perl -MDigest::HMAC_MD5 -MMIME::Base64 -le 'print encode_base64($ARGV[0] . " " . Digest::HMAC_MD5::hmac_md5_hex(decode_base64($ARGV[2]), $ARGV[1]))' holly hoge PDQwMTQ2OTQ3NjQuMTE0OTUxQGZlZG9yYTguZW5kbGVzcy1uYW1lbGVzcy54eHg+
amVycnkgZmQwZjU3ZGQ2NTJhMzcwNTQwZDYzMDYzMWVmN2YwZDE=
出力が得られた文字列をそのままtelnetに貼り付けてenterを押すとよい

syscall

いろいろ条件があって、system call使いたいが、それらを利用してperlモジュール使えない時の最終手段。/usr/include/asm/unistd_32.h あたりにsyscallでsystem callを呼び出す番号が定義されている

load average

#!/usr/bin/perl

use strict;

# /usr/include/linux/kernel.h:#define SI_LOAD_SHIFT       16
our $SI_LOAD_SHIFT = 16;
# /usr/include/asm/unistd_32.h:#define __NR_sysinfo      116
our $NR_SYS_INFO   = 116;

my $shift = 1 << $SI_LOAD_SHIFT;
my $buf = "\0" x 64;
syscall($NR_SYS_INFO, $buf) == 0 or die $!;

my @loads = map { $_ / $shift } (unpack "l L3", $buf)[1..3];
printf "load average: %.2f, %.2f, %.2f\n", @loads

モジュールなり、スクリプトにperlバージョン指定をする時

# こんなの
use 5.10.1;
# とかこんなの
use v5.10.1;
とか指定できるが、
use 5.010_001;
みたいな書き方にしとくこと

utf8 flag

perl以外の人は結構ひっかかるところ。

基本的な手順

  1. 外部からのデータ(ファイルから読み取った値、DBから取得した値、webappならGET/POSTなどで渡された入力パラメータなど)は全てutf8 flagをたてる
  2. 各処理はutf8 flagがたった状態で処理する(当然ながらuse utf8しとくこと)
  3. 外部へ出力する場合はutf8 flagを落とす

http://www.rwds.net/kuroita/program/Perl_unicode.h... とか今でもたまに見直す

何がうれしいか

はじめは文字化けが発生したりapacheのerror_logにwarning(内容忘れた)などがよく出力されていたが
  1. 正規表現でマルチバイトを使用可能
  2. 文字数を取得できる(byteで取得したい場合はbytes pragmaのbytes::lengthを使う。http://perldoc.perl.org/bytes.html
  3. 当然ながらその他の文字列系処理(substrとか)もマルチバイトを意識することなく処理可能(だと思う)
とはいえ、utf8 flagが鬱陶しいという場合(特にリファレンスを扱う時?)はUnicode::RecursiveDowngradeを使うといい

utf8 flagを扱う処理

まああんまり詳しくは書かないけど
utf8 pragma
必ず宣言すること。宣言するとそのパッケージ内に記述されているマルチバイトは全てperl内部表現文字列として扱われる。つまりuff8 flagがたった状態。
Encode
utf8 flagをたてる/落とすの基本的な処理を行うモジュール
  • encode
  • encode_utf8
  • decode
  • decode_utf8
  • is_utf8
  • from_to
このあたりの関数さえ使えれば何とかなる
Encode::Guess
文字コード判定用。文字コード判定できなかったときの処理さえ気をつければ問題ない。http://blog.livedoor.jp/dankogai/archives/50737353... ←これ

処理サンプル

ファイル
ファイルを読み取り時に取得したデータに自動的にutf8 flagをたてて、出力時にutf8 flagを落とす
#!/usr/bin/perl

use strict;
use utf8;

my $file = "aiueo.txt";
open my $fh, "<:utf8", $file or die "can not open $file. $!";;
chomp(my $data = <$fh>);
close $fh;

その都度指定がめんどくさい場合は
#!/usr/bin/perl

use strict;
use utf8;
use open IO => ":utf8";

my $file = "aiueo.txt";
open my $fh, "<", $file or die "can not open $file. $!";
chomp(my $data = <$fh>);
close $fh;

open my $fh, ">", "${file}.new" or die "can not open $file. $!";
print $fh "かきくけこ\n";
close $fh;
既に開いてしまっているファイルハンドル
binmode STDOUT, ":utf8";
binmode $fh, ":utf8";
標準入力
use strict;
use utf8;
use open ":utf8"; # use open IO => ":utf8" と同じ
use open ":std";

chomp(my $data = <STDIN>);
引数から受け取った文字列にutf8 flagを付ける
perl の-Cオプション(実際は-CA)か環境変数PERL_UNICODE(PERL_UNICODE=A って感じで使う)
DB
postgresの場合はDBD::Pgのpg_enable_utf8というものを使うといけると思う。
#!/usr/bin/perl

use strict;
use DBI;

my $dbname = "hogedb";
my $dbuser = "hogeuser";
my $dbpass = "dbpass";
my $dbh = DBI->connect("dbi:Pg:dbname=$dbname", $dbuser, $dbpass, {AutoCommit => 0, RaiseError => 1, pg_enable_utf8 => 1});

# anything to do

$dbh->disconnect;
DBD::mysqlの場合だとmysql_enable_utf8というのがある

その他

  1. Jcode.pmは使わない
  2. encoding pragmaも使わない(使わないほうがいいとかどっか書いてたような気がする)

Getopt::Longのいけてないところ

とにかくこうする。理由はここをみるとわかりやすい
use Getopt::Long qw(:config posix_default no_ignore_case gnu_compat);

profiling

Devel::NYTProfがよい
/usr/bin/perl -d:NYTProf foo.pl
とすると
nytprof.outというファイルが出来る。これがプロファイルの結果となるが、これだけでは結果はまだ見れないので
nytprofhtml

を実行するとnytprofディレクトリが出来るのであとはそのなかにあるindex.htmlを参照すればよい。なかなか見た目もいい感じ。
入ってないのならDevel::DProf(CORE Module)でも一応調べることは可能
/usr/bin/perl -d:DProf foo.pl
tmon.outというファイルが生成されるので、dprofppを実行する
Total Elapsed Time = 0.426661 Seconds
  User+System Time = 0.376661 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 18.5   0.070  0.080      1   0.0699 0.0796  YAML::Type::code::BEGIN
 7.96   0.030  0.039      7   0.0043 0.0055  Log::Dispatch::Config::BEGIN
 7.96   0.030  0.029      5   0.0059 0.0058  App::CLI::Plugin::DBI::BEGIN
 5.31   0.020  0.020      6   0.0033 0.0033  FindBin::BEGIN
 5.31   0.020  0.029      7   0.0028 0.0042  App::CLI::BEGIN
 4.25   0.016  0.115   1094   0.0000 0.0001  YAML::Base::__ANON__
 2.65   0.010  0.010      1   0.0100 0.0100  B::bootstrap
 2.65   0.010  0.010      1   0.0100 0.0100  utf8::AUTOLOAD
 2.65   0.010  0.010      1   0.0100 0.0096  Carp::longmess
 2.65   0.010  0.010      9   0.0011 0.0011  utf8::SWASHNEW
 2.65   0.010  0.010      9   0.0011 0.0011  Exporter::as_heavy
 2.65   0.010  0.010      4   0.0025 0.0024  App::CLI::Plugin::Parallel::ForkMa
                                             nager::BEGIN
 2.65   0.010  0.010     12   0.0008 0.0008  DynaLoader::dl_load_file
 2.65   0.010  0.010     14   0.0007 0.0007  warnings::unimport
 2.65   0.010  0.099      1   0.0099 0.0985  YAML::init_action_object
こんな感じでみることが可能

モジュール一覧

ExtUtils::Installedで
#!/usr/bin/perl

use strict;
use warnings;
use ExtUtils::Installed;

my $ei = ExtUtils::Installed->new;
map { printf "%s\t\t%s\n", $_, $ei->version($_) } $ei->modules;
消すこともできるけど、怖いからやめとく

perl module make後のテスト

たぶん自分でモジュールを配布する上で準備している場合によくやるやつ
perl Makefile.PL
make
この時点でblib/lib などにモジュールが展開されるのだが、make installとかしてしまうまえに、少しだけ簡単なスクリプトを書いてテストとかしたい場合
use ExtUtils::testlib;

# 今回作成したFoo.pmをload
use Foo;
とかするとblib/libのパスを通すとか細かいことを気にする必要がなくなる

json pretty

JSONモジュール使えばよい。aliasとかにしとくほうが便利
curl "https://foo.bar.api.com/return_json" | perl -MJSON  -ne 'BEGIN{$json = JSON->new}print $json->pretty->encode($json->decode($_))'
タグ

Wiki内検索

Menu

ここは自由に編集できるエリアです。

管理人/副管理人のみ編集できます