Perl Tips

この章では、Perlスクリプトを書いていて気付いた注意点やコツなどを書き連ねていく。

お品書き

`used only once' 警告を消す

設定変数だけを別ファイルに分けることは多いはず。しかし、その場合、 warnings を有効にすると、`used only once, possible typo' (この変数は一度しか使われていないぞ。ミススペルじゃないのか?) という警告が出ることが多い。これを防ぐには、本体スクリプトの冒頭で、

#!/usr/bin/perl
use warnings;
no warnings qw(once);

出力バッファリングを一時的に無効化する

或るファイルハンドルに対してのみ出力バッファリング (output buffering) を無効にするには、そのファイルハンドルを select() してオートフラッシュ変数 $|1 に設定する。select() を再び元の状態に戻しておくのを忘れてはならないが、ひとつの複合 select文で一息に行うことができる。(参照: perlfaq5)

open(FH, "+>", $file);
select((select(FH), $| = 1)[0]);

Perlでデーモンを書く

スクリプトをバックグラウンドに回してデーモン的に走らせるには、fork() ファンクションを使って自身のコピーを作ってから、当初のプロセスが自殺すればよい。このルーティンは ddclient のコードを真似させてもらった。このようなスクリプトを書けば、起動の時にわざわざ `script &' で裏に回す必要がない。留意点:

なお、スクリプト自体は fork したりバックグラウンド化せずに、daemontools を使ってデーモン化する手もある。djbdns のページの daemontoolsのインストール やそのコラムも参考にしていただきたい。

# Kill myself after launching a background one.
$SIG{CHLD} = 'IGNORE';
my $pid = fork;
 
# Fork failure.
if($pid < 0){
    exit -1;
}
# Fork success.
elsif($pid){
    exit 0;
}
 
# These below are executed in new (forked) process.
$SIG{CHLD} = 'DEFAULT';
write_pid() or exit 1;
 
open(STDOUT, ">/dev/null");
open(STDERR, ">/dev/null");
open(STDIN, "</dev/null");

XMLファイルをパースする

本格的には XML::Parser を駆使すべきところかもしれないが、XML::Parser を使いやすくしてくれる XML::Twig を使うとラクだ。こうしたモジュールを使用すれば、コメントも自動的に無視してくれる(->new() の際に comments=>'keep' あるいは comments=>'process' を指定すれば取り込むことも可能)。

スクリプト例で使用するサンプルXML (別テキストファイルで開く)

<?xml version="1.0" encoding="UTF-8"?>
<sampleconf>
 
  <common>
    <report> 
      <subject value='Hello'/>
      <to>
        <address value='foo@hoge.cxm'/>
        <address value='bar@hoge.cxm'/>
      </to>
    </report>
  </common>
 
  <db num='0001'/>
 
  <export dir='/report' title='true'>
    <post name='Test' error='4' address='false'>
      <column name='ユーザID'/>
      <column name='ユーザ名'/>
    </post>
  </export>
 
</sampleconf>

スクリプト例 基本部分:

#!/usr/bin/perl -w
use strict;
use XML::Twig;
 
my $conf = "./sample.conf.xml";
my $twig; # Twigを1回しか使わないならdisposeする必要がないのでグローバルスコープでの変数定義は不要、parse_confxml中で my すべき
 
sub parse_confxml {
    $twig = XML::Twig->new(keep_encoding => 1,       # マルチバイト文字を含むXMLの場合は指定した方がよい
            twig_roots => { db => 1, export => 1 }); # 指定のタグ下だけを取り込むことができる
    unless ($twig->safe_parsefile($conf)){           # parsefile() との違いに注目(後述)
	    return (undef,undef,undef);
    }
    my $root = $twig->root;

    ## (A) 後述のいろいろなパターンをここに差し替える##
}
 
my ($db,$dir,$title,$error,$address,@columns);
## (B) 後述のいろいろなメインルーティンをここに書く##
dbタグのnumプロパティと、exportタグのdir及びtitleプロパティを取り出す

A部;

my $db = $root->first_child('db');
my $export = $root->first_child('export');
return ($db->att('num'), $export->att('dir'), $export->att('title'));

B部;

($db,$dir,$title) = parse_confxml();
print "db=", $db, " dir=", $dir, " title=", $title, "\n";
$twig->dispose;    # メモリの解放

※ Twigを1回しか使わないなら dispose する必要はない。

dbタグのnumプロパティと、postタグのerror及びaddressプロパティを取り出す

A部;

my $db = $root->first_child('db');
my $post = $root->first_child('export')->first_child;
return ($db->att('num'), $post->att('error'), $post->att('address'));

B部;

($db,$error,$address) = parse_confxml2();
print "db=", $db, " error=", $error, " address=", $address, "\n";
$twig->dispose;

twig_roots で選択するタグは sibling (兄弟関係つまり同階層にある要素) でなくてもよいので、下記のようにすればメモリをさらに節約できる;

parse_confxml ファンクションの twig_roots のところをこうする;

        twig_roots => { db => 1, post => 1 });

A部;

my $db = $root->first_child('db');
my $post = $root->first_child('post');
return ($db->att('num'), $post->att('error'), $post->att('address'));

B部は前記と同じ:

($db,$error,$address) = parse_confxml3();
print "db=", $db, " error=", $error, " address=", $address, "\n";
$twig->dispose;
dbタグのnumプロパティと、postタグ内のcolumnリストの各nameプロパティを取り出す

A部;

my $db = $root->first_child('db');
my $post = $root->first_child('export')->first_child;
my @columns = $post->children('column');
return ($db->att('num'), @columns);

B部;

($db,@columns) = parse_confxml4();
print "db=", $db, "\n";
my $col;
foreach $col (@columns){
    print $col->att('name'), "\n";
}
$twig->dispose;

これら全部をひとまとめにしたサンプルスクリプト(twigtest.pl) をここに置いておく。

シェルスクリプト内で使える簡単日本語文字コード変換

日本語の文字コード変換ツールとしては nkficonv もあるが、経験から、Perl でやるのが最も確実だと思っている。Perl ではかなり前からもう Jcode.pm の機能が Encode モジュールとして標準装備されていて、別途 Jcode モジュールを組み込む必要はなくなった。下記は、Shift-JIS (CRLF改行) のファイルを引数で指定して `sjis2utf8.sh sjisfile.txt >utf8converted.txt ' といった具合に使い UTF-8 で出力するシェルスクリプトの例。まずは、長いが理解しやすい書き方から;

sjis2utf8_long.sh

#!/bin/sh
cat $1 |perl -MEncode=encode,decode -e '
    while (<STDIN>) {
      $_ =~ s/\r\n/\n/o;
      print encode("utf8",decode("shiftjis",$_));
    }'

もっと短い書き方をすると;

sjis2utf8.sh

#!/bin/sh
cat $1 |perl -MEncode=from_to -pe '
    s/\r\n/\n/o;
    from_to($_,"shiftjis","utf8");
    '

あるいはただ単に、

#!/bin/sh
perl -MEncode=from_to -pe '
    s/\r\n/\n/o;
    from_to($_,"shiftjis","utf8");
    ' $1

EUCを扱いたい場合は "euc-jp" と指定する。utf8 という指定と utf-8 という指定では実は挙動が異なるらしく、utf-8 のほうがよりコード体系に厳格なのだそうだ (utf-8utf-8-strict のエイリアス)。

Perlで日本語メールを送信

qmail のメール送信/負荷テストのために作ったスクリプト。Encodeモジュールを利用したものと、同 MIME-Header-ISO_2022_JP の欠如している環境用、Jcode.pm 利用版の 3種類を用意した。qmail の「動作テストのためのコマンド」の「送信テスト 4 (日本語メール) - jmailsend.pl」を参照いただきたい。