MacOSXの最近のブログ記事

SyntaxHighlighter 3.0

|

プログラム・コードを Web ページに掲載する際,コードを pre タグでマークアップするのが一般的ではないだろうか。SyntaxHighlighter を使うと,C/C++ や Java などの予約語をカラー表示したり,行番号を付加したり,重要な行を強調したりできる。これを使用しているプログラマのブログを目にするのは稀ではない。私も使ってみることにした。

いま現在の SyntaxHighlighter 最新バージョンは 3.0.83。ダウンロード・ページから syntaxhighlighter_3.1.0.83.zip を取得して,ローカル計算機で解凍する。解凍してできたディレクトリ下にある compass, scripts, styles, src のディレクトリを Web ドキュメントルート下にコピーすれば,使い始めることができる。ここでは DocumentRoot/sh にインストールしたものとする。

使い方を簡単に説明すると以下のとおりである。SyntaxHighlighter - Installation に書かれているので,きちんとした仕様が知りたい方はそちらを参照のこと。

  1. プログラム・コードを掲載したい HTML(以下「対象ページ」)のヘッダ部に shCore.jsshCore.css を読み込むよう指定する。
  2. 出力様式のタイプがテーマ Theme として管理されており,好みのものを選択して,そのスタイルシートを対象ページに指定する。一覧は SyntaxHighlighter - Themes にある。とくにこだわりがなければ標準 Theme: shThemeDefault.css を指定しておけばよい。私は Eclipse Theme について表示色等カスタマイズしたものを shThemeSite.css として保存して使うことにした。
  3. 対象ページで掲載したいプログラム・コードの言語に応じて SyntaxHighlighter の JavaScript を選択し,それらを対象ページに指定する。JavaScript の一覧は SyntaxHighlighter - Bundled Brushes にある。

上記までで対象ページのヘッダ部は以下のようなものとなる。ここではシェルスクリプト,XML(HTML 含む),Java,C/C++,Perl,JavaScript を扱うものとした。これは SyntaxHighlighter の出力例でもある。

  <link type="text/css" rel="stylesheet" href="/sh/styles/shCore.css" />
  <link type="text/css" rel="stylesheet" href="/sh/styles/shThemeDefault.css" />
  <script type="text/javascript" src="/sh/scripts/shCore.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushBash.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushXml.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushJava.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushCpp.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushPerl.js"></script>
  <script type="text/javascript" src="/sh/scripts/shBrushJScript.js"></script>

プログラム・コードは pre タグでマークアップする。class オプションに SyntaxHighlighter のコンフィギュレーションを指定する。扱う言語種別,行番号表示や開始番号,タブのサイズ,ハイライトすべき行番号,ツールバー表示有無,等である。SyntaxHighlighter - Configuration に指定可能なオプションの説明がある。以下は,Perl 言語コード,開始行番号 1,タブ・サイズ 4,ハイライト行なし,の指定である。title に題名を設定できる。

<pre class="brush: perl; first-line: 1; tab-size: 4; highlight: null;" 
     title="xlstocsv.pl">

行番号を出力したくない場合,gutter: false; を指定する。個別に行をハイライトしたいなら,highlight: [2, 4, 6]; のようにすればよい。プログラム・コードのマークアップが済んだら,最後に以下の JavaScript コードを書いておく。おまじないのようなものである。同一ページ内にいくつも SyntaxHighlighter 使用 pre タグを書いていても,最後に一度だけ発行すればよい。

<script type="text/javascript">SyntaxHighlighter.all()</script>

昨日作成した Excel 一括 CSV 出力の Perl コードは,以下のように表示される。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
# $Id: xlstocsv.pl 12 2012-02-05 16:57:06Z isao $
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

ただし,オリジナルの shBrushPerl.js では # 文字があるとそれ以降がすべてコメントとみなされてしまうので,$#ARGV など配列末添字番号変数の表示がおかしくなるので,上掲では少し訂正したものを用いた。53 行目の正規表現の先頭に [^\$] を追加し,以下のとおりに訂正した。

this.regexList = [
	{ regex: new RegExp('[^\$]#[^!].*$', 'gm'),	css: 'comments' },
	{ regex: new RegExp('^\\s*#!.*$', 'gm'),	css: 'preprocessor' },

ちょっと使ってみて制約があった。XML のタグのように < > で囲まれたテキストがあるとき,開始タグに対して終了タグがないと,終了タグが最後に強制的に付加されてしまう仕様のようである。空要素タグ < /> の場合も例外ではない。たとえば C/C++ の #include <stdio.h> の行をそのまま記述すると,終了タグを探しても当然ないので,ソースコード表示の最後に </stdio.h> なるゴミが出てしまうのである。私はまだ SyntaxHighlighter を使い込んでいないのでなんとも判断できないが,< > については,これまでどおり実体参照指定(&lt; &gt;)で記述した方がよさそうである。

漢詩詩語集を処理するに際して,Excel ファイルを CSV に一括変換するツール「Excel 一括 CSV」だと,JIS X 0212 のいわゆる JIS 第三・第四水準の文字の出力が「?」に化けてしまった。これをなんとかしたいと思い別のツールをいろいろ漁ってみたが,これというものが見当たらなかった。UNIX 系システムでの Excel ファイルのテキスト変換では,xlhtml というプログラムが昔から有名であるが,これとて日本語の扱いに関しては不十分なところがある。この調査の過程で,Windows でなくても動作する Perl Excel ファイルハンドリング・モジュール Spreadsheet::ParseExcel の存在を知り,これを使って一括変換ツールを自作することにした。このモジュールは標準ではないので,コマンドラインから cpan -i Spreadsheet::ParseExcel としてインストールする。

Excel は古いバージョンで作成したものだと,ファイル内の日本語文字コードが Shift_JIS なのか CP932 なのか UCS2 なのかよくわからない。おまけに私の目的として,出力すべきファイルは UTF-8 エンコードでなければならない。Spreadsheet::ParseExcelSpreadsheet::ParseExcel::FmtJapan という日本語処理のための下位モジュールを備えている。これらを併用すれば,日本語文字コードに煩わされずにすみ,JIS X 0212 文字もうまく処理でき,かつ UTF-8 で出力できることがわかった。Spreadsheet::ParseExcel のマニュアル(perldoc Spreadsheet::ParseExcel で閲覧できる)に API 解説とサンプルコードがあるので,参照いただきたい。

今回作成した Excel ワークシート一括 CSV 変換プログラム xlstocsv.pl を以下に掲げる。こんなに短いコードでできてしまうのなら,最初から自分の手を動かすべきであった。同じ課題をもつ方は必ずいらっしゃると思うので,以下のコードをコピって活用いただきたい。もちろん無保証,無サポートである。使い方は,コマンドラインで xlstocsv.pl 対象Excelファイル とする。引数に Excel ファイルを一つ指定する。すべてのワークシートが単一 CSV テキストストリームとして STDOUT に書き出される。ファイルに格納したいのならリダイレクトする。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
# Excel to CSV
# $Id: xlstocsv.pl 12 2012-02-05 16:57:06Z isao $
use strict;
use utf8;
use Spreadsheet::ParseExcel;
use Spreadsheet::ParseExcel::FmtJapan;
binmode STDOUT, ":utf8"; binmode STDERR, ":utf8";
($#ARGV < 0) && die "Usage: $0 xls-file\n";
my $fnm = $ARGV[0]; # Excel file name
my $ct = 0;         # 出力行数
# Excel object
my $xls = Spreadsheet::ParseExcel->new();
my $fmt = Spreadsheet::ParseExcel::FmtJapan->new(); # 日本語あり
my $bko = $xls->parse($fnm, $fmt); # Excel Book object
my @wsa = $bko->worksheets();      # Worksheet object 配列
utf8::decode($fnm);
# ワークシート毎の処理
foreach my $ws (@wsa) {
    my $wsnm  = $ws->{"Name"};
    my $mxrow = $ws->{"MaxRow"};
    my $mxcol = $ws->{"MaxCol"};
    print STDERR "*** $0 $fnm - sheet: $wsnm start.\n";
    if ((defined $mxrow) && (defined $mxcol)) {
        # 行毎の処理
        for (my $row = 0; $row <= $mxrow; $row++) {
            my $line = "";
            # 列毎の処理
            for (my $col = 0; $col <= $mxcol; $col++) {
                # セル値をセット
                my $cell = $ws->get_cell($row, $col);
                $line .= $cell->value() if (defined $cell);
                $line .= ",";
            }
            print "$line\n";
            $ct++;
        }
    }
}
print STDERR "*** $0 from $fnm wrote $ct lines\n";

もしワークシート毎にファイルをばらしたいのなら,foreach my $ws (@wsa) のブロック(ワークシート単位のループ)部分の始めと終わりに独自ファイル処理(「ファイル名+シート名」でファイルハンドルを割り当てる等)を入れればよい。また,このコードは CSV をカンマ区切り形式とし,かつセルの内容に半角カンマが含まれない前提である。もし半角カンマが含まれてもよいようにするなら,得られたセル値を出力時にダブルクォーテーションマークで括る,等の処置を施していただきたい。

これで,一つの Excel ファイルに夥しいワークシートがあろうが,いちいち Excel を開いて CVS として保存しなくても,コマンドラインから一発で CSV 変換ができるようになった。おまけに FreeBSD でも Linux でも Mac OS X でも動くのである。もちろん Windows でも動くし,Excel がインストールされていなくても Excel ファイルを処理出来る,というメリットがある。

私の課題 — いただいた詩語集の処理に関しては,71 の Excel ファイルそれぞれに何十ものワークシートがあって,Excel をひとつひとつ開いてワークシート毎に CSV エクスポートするのはとてもやっていられないが,xlstocsv.pl を使えば次のコマンド操作だけで二字詩語,三字詩語の CSV(2c.csv, 3c.csv)をまとめることができた。

isolde [1001] % foreach i (2c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 2c-$BS.csv) >& 2c-$BS.err
foreach? end
...
isolde [1002] % foreach i (3c-xls/*.xls)
foreach? set BS=`basename $i .xls`
foreach? (xlstocsv $i > 3c-$BS.csv) >& 3c-$BS.err
foreach? end
...
isolde [1003] % env LC_ALL=C sort 2c-*.csv | uniq > 2c.csv
isolde [1004] % env LC_ALL=C sort 3c-*.csv | uniq > 3c.csv

xlstocsv.pl は引数に Excel ファイルを一つしか指定できないが,複数の Excel ファイルを扱いたいなら,上の tcsh 操作のようにループで回せばよい。次のシェルスクリプトは,カレントディレクトリにあるすべての Excel ファイルを,「xls と同じファイルベース名+拡張子 csv」という名の CSV ファイルに書き出す。

#!/bin/sh
for i in *.xls
do
    BS=`basename $i .xls`
    xlstocsv.pl $BS.xls > $BS.csv
done

misima 漢詩・詩語集拡充

|

misima 漢詩作成支援の詩語検索機能で参照する詩語データベースを大幅に拡充した。ここのところ,この漢詩プログラム改造に取り組んでいた延長で,詩語のデータベース拡充のための元ネタがないか探していたら,今月 2 日,ある方から個人的に集めた詩語集をいただいた。入手した Microsoft Excel 形式のファイルを CSV に変換して行数を確認すると,なんと 8 万行もある。長年の労苦が偲ばれる素晴らしい成果をいただいた次第である。これは早速データベースにして,提供してくれた方に使ってもらわないとバチが当たる。昨夜と今日のお休み一日かけて,これに取り組んだのである。

いくつものシートに分けられた Excel データを CSV データに変換するのに,「Excel 一括 CSV」を使わせてもらった。妻の Windows 7 で変換したのち,Mac OS X にコピーして,ここからいくつかプログラムを書いて既存のデータベースにマージした。主な作業手順は以下のとおり。

  • 文字コードを UTF-8 に変換(nkf 使用)。
  • CSV データをフォーマット編集(新規作成ツール)
  • 平仄付加(既存ツール)
  • 詩語を旧字体変換して増幅(既存ツール)
  • 新旧両字体で検索してもヒットするよう韻字カラム調整(新規作成ツール)
  • 重複排除(既存ツール)
  • SQLite3 import
  • FreeBSD サーバ Tomcat 環境への辞書・プログラムのデプロイ(配備・インストール)
  • 読み,意味,出典も詩語検索の結果に出力する処理(JavaScript)も追加した。旧字体変換を入れたので,韻字の検索で新旧字体のどちらを指定してもヒットするようデータ構造,クエリを変えた。例えば,韻字が「仏」・平仄「○-●」の検索で,「銅佛」が出て来るようにした。
     

    20120204-sigo.png

    これまでの詩語データベースは,ネットで漁った『唐詩選』の漢詩データ 470 首程度を Perl HTML::TreeBuilder パーサで処理して二字,三字の詩語に分解し,この結果得た約 4000 語程度しか蓄積してなかった。これがいまや二字 33601 語,三字 53657 語,計 87258 語にまで拡張された。まだまだ使い勝手としては疑問が残るけれども,詩語ブラウザとしてはそれなりの規模が備わったのではないかと思う。

    今回作業で使用した「Excel 一括 CSV」ツールでは,残念ながら JIS 第三・第四水準の漢字が文字化けしてしまった。データベースにはこれらは含めなかった。これらを反映するのが次なる課題である。

    misima 漢詩 Tomcat, Log4j エラー

    |

    misima 漢詩作成支援の機能追加を終えて,Tomcat ログ,misimaKansiServlet ログを眺めていたら,気になるエラーが出ていた。システム自体は正常に動作しているので,別に構わないわけだけれども対処することにした。
     

    * * *

    一つ目のエラーは Tomcat-5.5(JSP / Servlet コンテナのデファクトスタンダード・ソフトウェアのちょっと古めのバージョン)/usr/local/tomcat55/logs/stdout.log に出ていたもの。

    log4j:WARN No appenders could be found for logger →(折返し。実際は 1 行)
        (org.apache.commons.digester.Digester.sax).
    log4j:WARN Please initialize the log4j system properly.
    

    二行目の initialize 云々でロガー(ログ管理システム)Log4j の初期化がうまく行っていないようである。misimaKansiServlet そのものは独自に Log4j のプロパティ・ファイルを読み出して Log4j を初期化している。つまり,以下のように,デプロイメントディスクリプタ web.xmlinit-param タグにログ・プロパティ・ファイルの場所を書いておき,misimaKansiServlet Java プログラムから参照して logger を初期化している。

    web.xml:

    <servlet>
      <servlet-name>misimaKansiServlet</servlet-name>
      <servlet-class>misimaKansiServlet</servlet-class>
      ...
      <!-- logging プロパティのパス -->
      <init-param>
        <param-name>logproperty</param-name>
        <param-value>
          /usr/local/tomcat5.5/webapps/misimakansiservlet/log4kansi.properties
        </param-value>
      </init-param>
    </servlet>
    

    misimaKansiServlet.java:

    public class misimaKansiServlet extends HttpServlet {
        /** log4j property */
        private static String prop = "log4kansi.properties";
        /** log4j logger */
        private static Logger mslog;
        ...
     
        /** 初期化 */
        public void init() throws ServletException 
        {   
            // web.xml デプロイメントディスクリプタから初期パラメータを取得する.
            String p = getInitParameter("logproperty");
            if (p != null) { prop = p; }
     
            // log file initialize.
            mslog = Logger.getLogger(misimaKansiServlet.class.getName());
            PropertyConfigurator.configure(prop);
            mslog.info("misimaKansiServlet initialize.");
            mslog.info("- log properties file: " + prop);
        }
    ...
    

    これで何でまだ初期化されていないなどと言われるのか。misimaKansiServlet のログは初期化されてきちんとロギングできるので,どうやら Log4j の親玉みたいなクラスがシステムとしてのロガーの初期化ができないということのようである。調べたら,このためのプロパティ・ファイルを Log4j がきちんと読めるようにしておけばよいとわかった。つまり,/usr/local/tomcat5.5/conf/log4j.properties に全体用プロパティ・ファイルが格納されているとして,Tomcat 起動時に参照する JAVA_OPTS 環境変数に以下を追加記述しておけばよい。

    -Dlog4j.configuration=file:///usr/local/tomcat5.5/conf/log4j.properties
    

    これで Tomcat を再起動すれば,エラーは出なくなった。私の FreeBSD 環境の場合 Tomcat 起動用スクリプトのなかで JAVA_OPTStomcat55_java_opts 環境変数から取得しているので,/etc/rc.conf に以下を追加した。

    tomcat55_java_opts="-Dlog4j.configuration=file:///usr/local/tomcat5.5/conf/log4j.properties"
    
     
    * * *

    二つ目のエラーは,misimaKansiServlet ログの DWR 関連メッセージである。

    12/01/30 18:24:18.079, ERROR, [TP-Processor6] org.directwebremoting.util.LogErrorHandler,
    42: Line=23 The content of element type "dwr" must match "(init?,allow?,signatures?)".
    

    文面からは何も原因を想定できない。DWR サイト内を検索したら,メーリングリストに同じエラーに遭遇した人の投稿とそれへの回答があり,難なく原因がわかった。misimaKansiServlet 用の DWR 定義ファイル dwr.xml の記述において,今回追加した Java クラスのために allow タグも追加したのだが,これがまずかった。このタグは複数書いてはいけない仕様だったのである(それでも追加プログラムが動いてくれるのが面白い)。よって,対策は一つの allow タグにすべて放り込む,というのでOKだった。

    <dwr>
      <!-- without allow, DWR isn't allowed to do anything -->
      <allow>
        <create creator="new" javascript="SigoTable">
          <param name="class" value="SigoTable" />
        </create>
        <convert converter="bean" match="SigoBean" />
      <!--
      </allow> 単一 allow にするため削除
      <allow>  同上
      -->
        <create creator="new" javascript="KanjiTable">
          <param name="class" value="KanjiTable" />
        </create>
        <convert converter="bean" match="KanjiBean" />
      </allow>
    </dwr>
    

    misima 漢詩作成支援・平仄音韻分析・詩語検索ツールに,旧字体変換機能と漢字検索機能を追加した。ただし,このツールは友人向けの限定公開であり,ユーザ ID,パスワードを入力しないとアクセスできないようになっている。

    今回の追加機能は,先日,自分で漢詩を書く際に本ツールを使ってみて,足りないと思った機能である。そのときは手元の仮想端末から SQL を叩いて漢字データベースを検索して,欲しい平仄・韻をもつ漢字を探す,などしていた。せっかくだから,これらを Web 上で出来るようにした。旧字体変換はすでにある misima 旧字・旧仮名遣い変換サーブレットを呼び出すようにしただけである。漢字検索機能は,詩語検索とまったく同じ方式である(「DWR with Java: misima 漢詩詩語検索」を参照)。DWR(Dynamic Web Remoting)ライブラリをベースに,Java Beans クラス(KanjiBean.java)と,漢字 DB を検索してそのクラス配列にデータをストアするメソッド(KanjiTable.java)の二本の短い Java プログラム,非同期通信ライブラリ DWR とユーザインタフェースを制御する一本の JavaScript(kanji.js),検索用 HTML(kanjisearch.html)をごそごそと書いた。DWR のおかげで面倒な Java サーブレットのコーディング,非同期通信 Ajax JavaScript のブラウザ依存コーディングから解放され,たったこれだけの一日作業で機能追加が出来た。以下,追加機能の使い方を簡単にメモしておく。

    メインの画面(図 1.)に「漢字検索」と「旧字体」のボタンがある。
     

    20120128-kansi-1.jpg

    図 1. メイン画面

    分析対象漢詩テキストを入力し,「旧字体」のボタンをクリックすると,入力した内容が旧字体に置き換えられる。図 2. はその実行前後を示している。


    20120128-kansi-2.jpg

    図 2. 旧字体変換

    「漢字検索」右横の「開く」ボタンをクリックすると,漢字検索用の別ウィンドウがオープンする(図 3.)。
     

    20120128-kansi-3.jpg

    図 3. 漢字検索ウィンドウ

    ここで「漢字」,「韻字」,「韻目」で検索が可能である。ある漢字の平仄,韻目を知りたいとき,「漢字」のテキストエリアに文字を指定する。複数指定することができる。「韻字」のエリアに漢字を入力して検索すると,当該漢字と同じ韻目の漢字の一覧が得られる。押韻文字を考えるときに役立つはずである。「韻目」は,韻目を指定して該当する漢字の一覧を検索するためのものである。韻目は「上平聲一東」のような複雑なものでありかつシステム内部において特殊な形式で管理している。この形式でユーザが入力するのは困難なので,韻目テキストエリアにカーソルが位置づけられるとメニューがポップアップし,ここに一覧された平水韻 106 項目から求める韻目を選択することにより,検索条件が設定されるようになっている。図 4. に韻目検索のポップアップメニューを示す。
     

    20120128-kansi-4.jpg

    図 4. 韻目ポップアップメニュー

    「漢字」,「韻字」,「韻目」の条件は1回の検索ではどれかひとつしか指定できない。複雑な条件で検索するために「SQL」条件を設けてある。検索キー:漢字 ji; 平仄 hs; 字韻1 in1; 字韻2 in2; 音読 yo; 訓読 yk; 備考 bk を使って,SQL where 句のなかに書くクエリ条件を記述する。select * from KANJITBL where query condition; の SQL 文のうち query condition 部分のみをテキストエリアに記述する。最後の ;(クエリ終了を示すセミコロン)も書いてはならない。これはエキスパート向けであり,基本的には使う場面はないと思う。

    検索結果には,漢字,平仄,韻目,備考が出力される。備考欄には,音読み,訓などの付加情報があったりなかったりする。出力例を図 5. に示す。
     

    20120128-kansi-5.jpg

    図 5. 漢字検索結果例

    ここで,平仄欄の は平字, は仄字, は平仄両韻字(意味によって平仄が違う)を示している。韻目欄の,例えば「hs:07:陽」というのは下平聲七陽を示している。hk: 上平聲,hs: 下平聲,sj: 仄上聲,sk: 仄去聲,sn: 仄入聲であり,コロンで区切られた数字と漢字一文字で韻目を表している。

    これで,漢詩の平仄・音韻規則チェック,詩語の検索,漢字の平仄・韻目検索が揃った。これからは詩語データベースの充実を図り,漢詩作成の有益なツールになるようにしたいものである。
     

    20120128-kansi-6.jpg

    図 6. misima 漢詩作成支援・平仄音韻分析・詩語検索・漢字検索

    寝正月。この年末年始は,堕落の一言。大掃除の手伝いもせず,芭蕉の俳句と北村薫『街の灯』を少し読むばかり。さすがに愛用の Mac くらいはキレイにすることにした。買ったばかりの Mac は,まるで美人の皓歯か雪肌のような眩しい白で持主をうっとりとさせてくれるが,使い倒すとヤニで汚れた歯でニタニタされるくらい鬱陶しく変貌してしまう。

    Mac US キーボードをウェットティッシュでゴシゴシ拭きながら CD を聴いた。門光子の演奏による現代日本のピアノ曲集『風の記憶』。M·A Recordings レーベルから 2002 年に出たレコード。吉松隆,三木稔,武満徹,西村朗,藤森守,柴山拓郎のピアノ小品が納められている。門光子はここで,知る人ぞ知るイタリア製ピアノの名器 Fazioli F278 (猫も杓子もコンサートグランドピアノは Steinway & Sons,じゃ困ります)を弾いている。

    『風の記憶』という題名に相応しく,夢の中で海浜に坐ってただひたすら風に吹かれて遠い彼方を見つめている,そんな,まるで映画のインプレッシブなシーンにいる感覚にさせてくれる。何と言っても,吉松隆『プレイアデス舞曲集』からの 5 曲がめっぽう愛らしい。Fazioli ピアノの特長なのか,門光子のタッチの気品なのか,柔らかく優しく朧な音像が絶品である。門光子演奏のピアノ曲集は,私にとって最高の現代の音の風景である。夢見るような気分にさせてくれるんである。

    この CD は残念ながら品切れもしくは廃盤になってしまっており,アマゾンマーケットプレイスでも出品がない。オークションサイトや中古レコード店で探し出して,ぜひ聴いていただきたい。
     

    20120102-kazenofukei.jpg
     

    Mac のキーボードもキレイになりました。
     

    20120102-macg5.jpg

    20120102-macbook.jpg

    Apache IP denial configuration

    |

    Apache のアクセスログを時折チェックすると,様々なアタックがあることに気付く。公開 Web サーバを運用しているとこれはもうしようがない。GET //dbadmin/scripts/setup.php HTTP/1.1 などの要求(世に言う php アタック)が頻発していて,すべて 404 レスポンス(document not found)で弾かれている。404 で帰ってくれるならよいのだが,隠れたセキュリティホールを突かれてサーバが乗っ取られないとも限らない。面倒だが,これら前科のあるヤクザ・ホストの IP アドレスを収拾し,こいつらのネットワークから来る HTTP 要求はすべて拒否することにした。

    Apache には特定の IP アドレス,ネットワークからの要求を拒絶する機能が備わっている。あるドキュメントに対して deny from IP | CIDR を指定すればよい。ヤクザ・ホストの IP アドレスをログから抽出しここに指定しておけば,Apache は 403 レスポンス(forbidden)を返し,アクセスを拒絶してくれる。今回は IP アドレスそのものではなく,こいつらヤクザが属しているネットワーク全体からの要求を拒絶するように CIDR(ネットワークアドレス/マスクビット)を指定することにした。暴力団排除条例が施行されたことだし,「関係者」ともども皆お断りというわけだ。今日は,その設定変更レシピの備忘録を示しておく。ここで % は UNIX コマンドライン tcsh プロンプトを示している。

    1. 存在しないドキュメント参照記録の抽出
    2. まず 404 の履歴を Apache のアクセスログから抽出する。user, webserver は,それぞれ Web サーバへのログイン ID,ホスト名である。リモートシェルを使って Mac OS からオペレーションしている。
      % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log > 404list.txt
      
    3. アタックの特定
    4. 404list.txt をテキストエディタで開いて内容を確認し,明らかにアタックだと思われるアクセス(.cgi, .dll, .php の拡張子をもつ,いかがわしいプログラムを実行するもの)を特定し,それ以外の行を削除して格納する。

    5. IP アドレス・リストの作成
    6. 編集済のログデータ 404list.txt から IP アドレスだけを抽出・編集し,重複を排除し,対象リスト 404ip.txt を作成する。
      % cat 404list.txt | cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
      
      私の場合,上記 1〜3 のオペレーションは以下のワンライナーでよさそうであった(→印は,次の行と一続きで入力することを示す)。
      % rsh -l user webserver grep -e ' 404 ' /var/log/httpd-access.log  | \
      grep -v 'GET .*\.html ' | grep -e 'bbs\.html\+\|\/manager\|\.php\|\.dll\|\.asp\|\.pl\|→
      GET http\|\/user\/\|\/muieblackcat\|blackhats\|++++++*\|GET \/\/\|GET \/crossdomain\|→
      GET \/idle\/\|GET \/appConf' | \
      cut -f 1 -d ' ' | grep -e '[0-9]' | sort | uniq > 404ip.txt
      
    7. Whois 情報から Apache denial list の作成
    8. 404ip.txt 中の IP アドレスで whois を参照し,このアドレスが属するネットワーク CIDR や国コードを取得する。国コードはなくてもよいが,いったいどの国にヤクザが多いのか興味はありませんか? この処理は UNIX の whois コマンドを IP アドレス毎にひとつひとつ叩いて CIDR を調べ,Apache の定義文を作成すればよいけれども,IP の数が多いとやってられない。ネットワークの IP アドレス範囲から CIDR を計算しなければならない場合もあり,面倒だ。そこで私は,これらを全部自動的に行う Perl コード ipwhois.pl を書いた。ipwhois.pl コードを記事の最後に掲げておく。このプログラムは IP アドレス・リストを標準入力から読みとって,IP アドレス毎に whois を照会し,当該 IP の国コードと CIDR を取得あるいは計算し,指定出力ファイルに Apache の拒絶定義文(deny from CIDR)の形式で出力するとともに,標準エラー出力に国コード毎の集計データを出力する。CIDR が得られなかったら IP アドレス単体指定を行うものとしている。さて,私の自宅サーバログに基づく実行結果は以下のとおり。標準エラーの全出力を掲げておきます。ヤクザ IP・CIDR の一覧でもありますので,お使いいただいても構いません。
      % ipwhois.pl -o denylist < 404ip.txt 
      113.59.33.9     CN      113.59.0.0/17
      113.84.73.25    CN      113.64.0.0/11
      119.254.72.218  CN      119.254.0.0/15
      121.14.129.100  CN      121.8.0.0/13
      121.141.172.40  KR      121.128.0.0/11
      121.166.70.252  KR      121.160.0.0/11
      140.112.41.58   TW      140.112.0.0/16
      173.162.102.20  US      173.162.64.0/18
      173.245.70.17   US      173.245.70.0/24
      175.177.100.182 JP      175.177.100.0/24
      182.20.206.193  JP      182.20.128.0/17
      184.107.108.37  CA      184.107.108.0/24
      188.127.230.124 RU      188.127.224.0/20
      188.143.233.14  RU      188.143.232.0/23
      188.143.233.160 RU      188.143.232.0/23
      188.143.233.34  RU      188.143.232.0/23
      188.163.105.52  UA      188.163.0.0/17
      188.163.65.234  UA      188.163.0.0/17
      188.163.67.245  UA      188.163.0.0/17
      188.163.67.254  UA      188.163.0.0/17
      188.40.106.11   DE      188.40.0.0/16
      188.40.53.213   DE      188.40.0.0/16
      190.144.175.133 CO      190.144.175.133 (CIDR 190.144/14 not available)
      193.105.210.42  UA      193.105.210.0/24
      195.68.223.44   UA      195.68.222.0/23
      202.19.227.40   JP      202.19.227.40   (CIDR 2 not available)
      203.142.24.17   SG      203.142.24.0/24
      203.180.234.16  JP      203.180.234.0/24
      203.238.185.121 KR      203.238.176.0/20
      204.111.110.29  US      204.111.0.0/16
      204.9.123.122   US      204.9.120.0/21
      208.78.244.107  US      208.78.240.0/21
      209.144.30.98   US      209.144.30.0/24
      209.172.57.77   CA      209.172.32.0/19
      209.190.38.98   US      209.190.38.96/29
      209.236.115.222 US      209.236.112.0/20
      210.165.234.248 JP      210.165.128.0/17
      210.253.108.17  JP      210.253.96.0/19
      211.144.82.8    CN      211.144.64.0/19
      211.233.71.243  KR      211.233.64.0/20
      211.62.35.217   KR      211.62.32.0/21
      211.68.122.12   CN      211.68.120.0/22
      211.72.32.244   TW      211.72.0.0/17
      212.138.82.23   SA      212.138.64.0/18
      212.54.226.117  IT      212.54.224.0/19
      216.171.171.163 US      216.171.160.0/20
      219.136.241.165 CN      219.136.241.165 (CIDR 6 not available)
      221.117.61.178  JP      221.117.61.176/29
      222.186.43.119  CN      222.184.0.0/13
      31.128.142.199  RU      31.128.128.0/19
      31.184.236.13   UA      31.184.236.0/24
      31.184.236.33   UA      31.184.236.0/24
      31.184.236.34   UA      31.184.236.0/24
      31.184.236.36   UA      31.184.236.0/24
      31.184.238.71   RU      31.184.238.0/24
      46.109.177.176  LV      46.109.0.0/16
      46.116.43.68    IL      46.116.0.0/15
      46.21.154.66    US      46.21.144.0/20
      58.191.154.41   JP      58.191.154.40/29
      58.215.12.42    CN      58.208.0.0/12
      59.108.108.100  CN      59.108.0.0/16
      61.187.206.148  CN      61.187.206.148  (CIDR 4 not available)
      61.190.172.2    CN      61.190.0.0/16
      64.124.203.73   US      64.124.0.0/15
      66.87.2.132     US      66.87.0.0/16
      68.15.157.99    US      68.0.0.0/12
      68.58.54.214    US      68.32.0.0/11
      69.133.106.156  US      69.132.0.0/14
      69.26.37.39     US      69.26.32.0/19
      69.73.163.187   US      69.73.128.0/18
      72.29.84.41     US      72.29.64.0/19
      72.44.90.84     US      72.44.80.0/20
      74.217.148.71   US      74.217.0.0/16
      74.217.148.72   US      74.217.0.0/16
      77.207.110.229  FR      77.192.0.0/12
      77.247.181.165  NL      77.247.176.0/21
      77.52.112.197   UA      77.52.64.0/18
      77.92.224.110   GE      77.92.224.0/24
      77.92.233.198   GE      77.92.233.0/24
      81.170.143.96   SE      81.170.128.0/17
      81.25.46.137    BY      81.25.32.0/20
      82.193.117.23   UA      82.193.96.0/19
      85.192.15.82    RU      85.192.0.0/20
      85.88.195.35    IT      85.88.192.0/19
      86.101.224.233  HU      86.101.0.0/16
      86.127.192.137  RO      86.120.0.0/13
      88.40.179.242   IT      88.40.0.0/15
      89.107.227.210  TR      89.107.227.0/24
      89.252.58.228   UA      89.252.0.0/18
      89.28.124.238   MD      89.28.0.0/17
      89.97.190.170   IT      89.97.0.0/16
      91.135.235.139  GB      91.135.224.0/20
      91.207.6.154    UA      91.207.6.0/24
      91.207.6.174    UA      91.207.6.0/24
      91.207.6.82     UA      91.207.6.0/24
      91.214.45.239   LU      91.214.44.0/22
      91.224.160.126  NL      91.224.160.0/23
      91.224.160.127  NL      91.224.160.0/23
      91.224.161.127  NL      91.224.160.0/23
      91.226.165.164  RU      91.226.164.0/22
      92.113.10.162   UA      92.113.0.0/18
      93.182.36.82    RU      93.182.32.0/20
      93.84.116.216   BY      93.84.0.0/15
      94.24.152.61    RU      94.24.128.0/17
      Country list:
      GB = 1; GE = 2; NL = 4; TW = 2; LV = 1; BY = 2; SA = 1; KR = 5; RU = 10; MD = 1;
       TR = 1; JP = 8; CA = 2; SE = 1; CN = 12; DE = 2; LU = 1; CO = 1; RO = 1; SG = 1
      ; IT = 4; UA = 17; US = 21; IL = 1; HU = 1; FR = 1; 
      
    9. Apache denial list 重複削除
    10. ipwhois.pl が出力した denial list denylist から CIDR が重複するものを一つに纏める。
      % cat denylist | sort | uniq > denylist.txt
      
    11. Apache httpd.conf への反映
    12. 完成した denial list denylist.txt を Apache のコンフィグレーション(httpd.conf あるいは,これからインクルードされる定義ファイル)中のドキュメントルート Directory ディレクティブのところに,テキストエディタを使って反映する。以下はその例。このあと定義文をチェックし(apachectl -t),問題なければ,Apache を再起動(apachectl restart)して,作業は完了である。
      <VirtualHost *:80>
          ServerName yasuda.homeip.net
          DocumentRoot /usr/local/www/apache22/data
          <Directory />
              Options ExecCGI Includes FollowSymLinks
              AllowOverride None
          </Directory>
          <Directory "/usr/local/www/apache22/data">
              Options ExecCGI Includes FollowSymLinks
              AllowOverride AuthConfig
              Order allow,deny
              Allow from all
              # 以下 denial list 内容の CIDR を指定する
              deny from 113.59.0.0/17
              deny from 113.64.0.0/11
              deny from 119.254.0.0/15
              deny from 121.128.0.0/11
              deny from 121.160.0.0/11
              deny from 121.8.0.0/13
              deny from 140.112.0.0/16
              deny from 173.162.64.0/18
              deny from 173.245.70.0/24
              deny from 175.177.100.0/24
      ...
      

    さて,ipwhois.pl の実行結果から,我が家のサーバ・アタック杯ヤクザ国家の発表です。アタック元 IP の国コードとして多いのは,順に,一位:米国 21 ポイント,二位:ウクライナ 17 ポイント,三位:中国 12 ポイント,四位:ロシア 10 ポイント,五位:日本 8 ポイント,六位:韓国 5 ポイント,七位:オランダ,イタリア 4 ポイントでした。米国,中国,ロシアが上位に来るのは予想がつきましたが,ウクライナの二位は意外でした。もちろんこの国コードはサーバから見ての要求元なので,中国のヤクザが米国のプロクシ経由で攻撃したものは米国に分類されているはずである。

    ipwhois.pl コードは以下のとおり。コピペして使っていただいてもよい(無保証)。whois の項目を調べるための Perl モジュール Net::Whois::IPNet::CIDR が必要なので,動かすには事前に cpan コマンドでシステムにモジュールをインストールしておく必要がある。
     

    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    # Get ip-route and generate deny list from whois info
    # $Id: ipwhois.pl 5 2011-12-11 12:18:13Z isao $
    # 2011 (c) isao yasuda.
     
    use strict;
    use Net::Whois::IP qw(whoisip_query);
    use Net::CIDR qw(:all);
    use Getopt::Std;    # command line processing
    use File::Basename; # get file basename
    binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
     
    # command line.
    my %opts = ('o' => "0");
    Getopt::Std::getopts('o:', \%opts) ||
        die "Usage: " . basename($0) . " -o denylist \< (stdin)\n";
    my %ccode; # 国コード集計カウンタ配列
    if ($opts{'o'}) { # deny list open
        open(DL, ">> $opts{'o'}") || die "$opts{'o'} open failed.\n";
    } else { # 指定がなければ標準出力
        open(DL, ">-");
    }
    # IP 毎の処理
    while (<STDIN>) {
        chomp($_); $_ =~ s/\s+//g;
        # Whois 情報取得
        my $ip = $_;
        my $response = whoisip_query($ip);
        # 国コード取得
        my $country;
        if ($response->{country}) {
            $country = $response->{country};
        } else {
            $country = $response->{Country};
        }
        $country =~ s/\s+//g; $country =~ tr/a-z/A-Z/;
        # CIDR 取得 なければ IP をセット
        my $cidr; my $comm = "";
        if ($response->{route}) {
            $cidr = $response->{route};
        } else {
            if ($response->{CIDR}) {
                $cidr = $response->{CIDR};
            } else { # IP 範囲から CIDR を計算
                my $range = $response->{inetnum}; $range =~ s/\s+//g;
                $cidr = Net::CIDR::range2cidr($range);
                unless ($cidr =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+$/) {
                    $comm = "\t(CIDR $cidr not available)";
                    $cidr = $ip;
                }
            }
        }
        # 複数 CIDR が得られた場合はじめの一つのみにする
        my @acidr = split(/,/, $cidr);
        if ($#acidr) {
            $cidr = $acidr[0]; $cidr =~ s/\s+//g;
            $comm = "\t(Some CIDRs found. Selected one)";
        }
        # 端末出力
        print STDERR "$ip\t$country\t$cidr$comm\n";
        # Apache 用 deny list 出力
        print DL "deny from $cidr\n";
        # 国コード集計
        if (exists($ccode{$country})) {
            $ccode{$country} += 1;
        } else {
            $ccode{$country} = 1;
        }
    }
     
    # 国コード集計出力.
    print STDERR "Country list:\n";
    foreach (keys %ccode) { print STDERR "$_ = $ccode{$_}; "; }
    print STDERR "\n";
    close(DL);
     
    
     

    追記:

    こんなことをやっていたために,クラブワールドカップ・柏レイソル vs 北中米カリブ海代表モンテレイの試合を見忘れてしまった。柏レイソルが PK で勝利した。すげぇ。ベスト 4 進出。次はブラジル王者サントスとの対戦。楽しみじゃ。
     

    12.12 追記:

    whois から複数の CIDR が返却される場合があるため,その場合一つだけ採用するよう ipwhois.pl を訂正した。

    私の書斎の Power Mac G5 Mac OS X Tiger はもう 5 年以上使っている。今日,Facebook でページ埋込 YouTube 動画を観ようとしたら,"Flash Player upgrade required" なるエラーが出てダメになった。Flash Player の新しいバージョンを入れろということだけど,もはや Adobe は Mac PowerPC をサポートしなくなっていた。買い替えるしかないってか? Photoshop や Illustrator,Acrobat などなど含めて新しくするとなると溜め息の出るくらいのカネが要る。クッソ。

    * * *

    田中氏を更迭 女性や沖縄を侮辱」(産經新聞配信)なる記事を読んだ。先頃,野田内閣成立直後に閣僚がオフレコで軽口を叩いてマスコミにスッパ抜かれてクビになった。この田中さんもジョーダンのつもりだったのだろうが,レイプに喩えるなんていけません。沖縄県民なら,すぐ米軍兵士の少女陵辱事件を連想してしまうはずであるから。沖縄県民は官僚に染み付いた「沖縄県に対する差別意識」を読みとって轟々たる非難。当然である。田中さん,更迭くらいでよかったですね。私が彼の立場なら怖くて夜に外を歩けなくなるに違いない。こんなセクハラ公務員は沖縄のみならず日本女性全体の敵なのだから。

    ったく,日本の防衛省「役人」ってのは何でこんなに脇が甘いのか。余計なことをペラペラしゃべって「これオフレコだかんね」で暴言を吐いて,明るみに出たとたんに発言を曖昧にして責任のがれ。防衛省は軍隊である。田母神・元幕僚長(愚にもつかない「論文」によって防衛省軍人トップがいかにセンチメンタルで頭が悪いかを世界に知らしめた。ロシアのジョークに,「スターリンは馬鹿だ」と叫んだ男が秘密警察に「国家機密漏洩容疑」でしょっぴかれるというのがあるが,田母神さん「論文」は「僕は馬鹿だ」と叫んで国家機密,つまり日本国軍人トップの悲しい知的レベルを漏洩してしまったようなものである。ああ,MI6 の皮肉屋がこの論文を読んでホクソ笑んでいるのが目に見えるようだ)といい,この田中・元沖縄防衛局長といい,この口の軽さは皇国の軍属に相応しくない。去って当然である。

    沖縄県知事・仲井真さんはコメントを求められて「口が汚れるから何も言いたくない」と述べた。防衛省はもう沖縄県民からセクハラ官庁のレッテルを貼られたも同然である。こんな体たらくでは,普天間問題は日本政府の画策によっては解決しない。沖縄県民の民意が結局勝つだろう。米国も最近の対中戦略としてグアムを重視しはじめており,沖縄に米軍がいて欲しくてしようがない日本政府は,思うに,沖縄県民とともに米国にハシゴを外され最後は笑者になって,辺野古移設問題は終わる。
     

    付記

    この問題について「オフレコ」だったのに記事にした琉球新報を批判する人がいる。オフレコであろうがなかろうが,社会的責任の大きい話者がその職務に係ることで反社会的言動(防衛上沖縄を管轄すべき責任者が沖縄をレイプされる女性に喩えるなんて,沖縄をハラのなかでナめている)をなしたとなると,社会に知らしめるべきである。そしてしかるべき筋にその反社会的言動の始末をつけさせるべきである。この失言は彼の職務に対するスタンスの現われだからだ。企業幹部が重要顧客のことをレイプ対象の女と同じだなどと第三者に対して公言したとなると,普通の企業なら社長はその幹部を懲戒解雇にし,その首をもって顧客に謝罪するはずだ。だってペーペーの一担当者ならまだしも幹部となると企業体質そのものとして発言が受けとられ,当社への重要顧客の信頼を完膚無きまでに失墜させ,もう二度と取引してもらえなくなるからである。いま政府は沖縄県に対してこれと同じ関係にある。普通の企業なら当たり前のことが通用しないこの官僚の非常識はいったい何なんだ? もうこれ以上は言うまい。口が汚れる。

    オフレコなので,記事における沖縄防衛局長発言はおそらく琉球新報記者の記憶に基づいた再現なのだろうが,記者は局長発言に沖縄に対する侮りを読みとって,相当カツンと来て問題視したからこそ,オフレコやぶりをしたに違いない。
     

    11.5 追記

    この件についていま一川防衛大臣の引責辞任ばかりが取り沙汰されている。部下のクビはさっさと切っておいて上役はごめんなさいで済むのか,というわけである。こんなバカな責任連鎖があるものか。無意味な無限責任追求は,本当の問題の所在が曖昧になるため,無責任と同じである。一川さん自身の防衛大臣としての仕事そのものに問題があるならまだしも,田中さんは田中さん自身の人間性の個人的欠陥によって更迭されたのであって,それが何故に組織的引責問題に発展しなければならないのか私にはさっぱりわからない。米兵による少女陵辱事件について「詳しく知らない」と言った一川防衛大臣の「勉強不足」はまた別問題である。

    もしこの田中暴言問題が田中さんの個人的欠陥ではなく組織構造によるものだとしたら,その構造こそを明らかにすべきなのである。これで一川さんが辞任するとなると,田中体質が防衛庁そのものの体質であると逆に認めることになる。そうでなければ辞任すべきではない。野田総理はそのへんわかっているようである。

    自民党と筆頭とする野党はこんなことでしか与党を追求できないということ。自民党政権時代から田中さんは,問題を惹起こした差別意識をもって,沖縄で仕事をしてたんじゃないんでしょうか。今回の問題は潜水艦が浮上しただけじゃないのでしょうか。無限責任を追及し出したら,自民党時代のその責任はどうなるのか,ということになりませんか? そしてマスコミはこのテのスキャンダルにフォーカスすることでしか世論を醸成できないということ。元与党・自民党,マスコミのこうしただらしなさこそ政治的荒廃を生んでいる大本のような気がします。もっと政治エネルギーをかけるべき問題があるのではないでしょうか。

    この件,最近また韓国で従軍慰安婦問題が騒がしくなって来ているタイミングなので,日本人は官僚ですらジェンダーや性的観念においておかしな国民であると思われかねないので,腹立たしくてならないのである。この問題は,ポルノグラフィを愛好するのとは根本的に違うのだ。「このポルノ女優が好きだ」とは言うのは大いに許されるが,「あなたはポルノに合っている」などとは決して言ってはならないのである。

    麻雀点数集計プログラム

    |

    サッカー五輪予選を観ながら,Web 無料麻雀ゲームをした。われらが日本代表が一生懸命アウェイで頑張っているというのに,観ている方はなんと暢気なものか。半荘を 10 回もやり,珍しく一人勝ち。
     

    20111120-majan.jpg
     

    バブル時代の高レート勝負だったらいったいいくら稼いだのかちょっと知りたくなって,プログラムを書いて計算してみた。プログラムは mjcalc。Perl である。

    mjcalc -u [ウマ] -b [ビンタ] -k [レート] < 点数データ
    
    ここで,[ウマ] は "数字1-数字2" の形式で指定し,"数字1" は 3 位から 2 位への,"数字2" は 4 位から 1 位へのウマとなる。省略すると 10-20 が仮定される。[ビンタ], [レート] は数値を千単位で指定する。省略すると,それぞれ 100, 2 を仮定する。点数データは空白文字区切りの 4 つの整数値である。

    計算の考え方は次のとおり。25000 点持ちの 30000 点返し。オカは 20000 点とする。入力データは,1000 点 1 ポイントとして換算された各プレイヤの点数が空白文字で区切られているものととする。入力の点数を合計し,0 の場合はすでにウマ,オカが反映されているものとしてレート計算のみ行う。入力点数の合計が 100 の場合は,半荘終了時の持点として,指定されたウマ,オカを計算に反映する。指定されたビンタ,レートで金額計算を行う。ビンタとは下位のプレイヤが上位者に支払う差ウマのことで,ビンタ 10 万の取り決めならば,上位者に 10 万,自分が配給原点以下の場合,配給原点以上の上位者にはその倍額の 20 万を支払わなければならない。独り沈みの 4 位者ならば 60 万のマイナスというわけである。

    さて,mjcalc で計算してみる。デカリャンピン (1000 点 2 千),ビンタ 10 万の気違いレート。データは上に示した 10 回のデータ (point.txt) である。この Web 麻雀ゲームの集計値はすでに 10-20 のウマ,20 ポイントのオカ,30 ポイントとの差分が集計済みである。

     
    isolde:/Users/i-yasuda/src/tools[1125] % cat point.txt 
    60      -20     2       -42
    18      -21     -52     55
    63      9       -20     -52
    52      -21     3       -34
    11      -16     42      -37
    -29     67      6       -44
    10      -32     -21     43
    63      -21     2       -44
    40      5       -28     -17
    70      -30     -41     1
    isolde:/Users/i-yasuda/src/tools[1126] % ./mjcalc -u 10-20 -b 100 -k 2 < point.txt 
    ウマ1: 10; ウマ2: 20; オカ: 20(以上pt); ビンタ: 100(千円); レート: 2(千円/1pt)
    ----------------------------------------------------------------------
      Input Points     ;  Calculated Points ;   Amount of Money (k-en)   ;
       A    B    C    D;    A    B    C    D;      A      B      C      D;
    ----------------------------------------------------------------------
     +60  -20   +2  -42;  +60  -20   +2  -42;   +720   -240     +4   -484;
     +18  -21  -52  +55;  +18  -21  -52  +55;   +336   -342   -604   +610;
     +63   +9  -20  -52;  +63   +9  -20  -52;   +626   +318   -340   -604;
     +52  -21   +3  -34;  +52  -21   +3  -34;   +704   -242     +6   -468;
     +11  -16  +42  -37;  +11  -16  +42  -37;   +322   -332   +584   -574;
     -29  +67   +6  -44;  -29  +67   +6  -44;   -358   +634   +312   -588;
     +10  -32  -21  +43;  +10  -32  -21  +43;   +320   -564   -342   +586;
     +63  -21   +2  -44;  +63  -21   +2  -44;   +726   -242     +4   -488;
     +40   +5  -28  -17;  +40   +5  -28  -17;   +580   +310   -556   -334;
     +70  -30  -41   +1;  +70  -30  -41   +1;   +740   -260   -482     +2;
    ----------------------------------------------------------------------
    +358  -80 -107 -171; +358  -80 -107 -171;  +4716   -960  -1414  -2342;
     
    

    うん。半荘 10 回で 471.6 万を獲得したというわけである。でも,なんでこんな意味のないことをやっているのか。で,プログラムを書きながら計算の確認のため,やっぱりこの麻雀ゲームをやっていたら,なんと四暗刻を,しかもピンズ一色・嶺上開花(意味無し)でツモってしまいました。
     

    20111124-suanko.jpg
     

    一応,プログラム mjcalc のコードを掲げておきます。ヒマなときにこんなことしかしない,私のような方のために。

     
    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    #  Mahjong Points Calculator
    #  -------------------------
    #  Usage: mjcalc -u 10-20 -b 100 -k 2 < data
    #    -u 10-20: ウマ 10-20 (ワンツー)
    #    -b 100: ビンタ 100K円
    #    -k 2: レート 1000点 2K円
    #  2011(c) isao yasuda.
     
    use strict;
    use utf8;
    use Getopt::Std;    # command line processing
    use File::Basename; # get file basename
    binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
     
    # command line
    my %opts = ('u' => "0", 'b' => 0, 'k' => 0);
    Getopt::Std::getopts('u:b:k:', \%opts) ||
        die "Usage: " . basename($0) .
        " [ -u uma-range -b binta-k -k k-en ] \< (stdin)\n";
    my $umai1 = 10; my $umai2 = 20; my $okai = 20;
    my $uma1 = 0; my $uma2 = 0; my $oka = 20; my $kaesi = 30;
    my $binta = 100; $binta = $opts{'b'} if ($opts{'b'});
    my $krate = 2;   $krate = $opts{'k'} if ($opts{'k'});
    if ($opts{'u'}) {
        unless ($opts{'u'} =~ /^[0-9]+-[0-9]+$/) {
            die "-u operand invalid.\n";
        }
        ($umai1, $umai2) = split(/-/, $opts{'u'}, 2);
        $uma1 = $umai1; $uma2 = $umai2;
    }
    if (($uma1 == 0) && ($uma2 == 0)) {
        $oka = 0; $kaesi = 0;
    }
    if ($oka != 0) {
        print STDERR "ウマ1: $uma1; ウマ2: $uma2; オカ: $oka(以上pt); ";
    }
    print STDERR "ビンタ: $binta(千円); レート: $krate(千円/1pt)\n";
    my %totalpt = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # ポイント総計
    my %totalmn = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # 金額総計
    my %totalin = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # 入力ポイント総計
    print
        "----------------------------------------------------------------------\n";
    print
        "  Input Points     ;  Calculated Points ;   Amount of Money (k-en)   ;\n";
    print
        "   A    B    C    D;    A    B    C    D;      A      B      C      D;\n";
    print
        "----------------------------------------------------------------------\n";
     
    # Main
    while (<STDIN>) {
        utf8::decode($_); chomp($_);
        my @tensu = split(/\s+/, $_, 4);
        my $sowa = $tensu[0] + $tensu[1] + $tensu[2] + $tensu[3];
        unless (($sowa == 100) || ($sowa == 0)) {
            print STDERR "点数総和 $sowa は矛盾します。無視します。\n";
            next;
        } else {
            if ($sowa == 100) { # 持ち点
                $oka = $okai; $uma1 = $umai1; $uma2 = $umai2; $kaesi = 30;
            } else {            # すでにオカ・ウマ計算済
                $oka = $uma1 = $uma2 = $kaesi = 0;
            }
        }
        # プレーヤ-点数 配列
        my %plpt = ('A'=>$tensu[0],'B'=>$tensu[1],'C'=>$tensu[2],'D'=>$tensu[3]);
        # input print
        printf("%+4d %+4d %+4d %+4d; ",
               $plpt{'A'}, $plpt{'B'}, $plpt{'C'}, $plpt{'D'});
        $totalin{'A'} += $tensu[0];
        $totalin{'B'} += $tensu[1];
        $totalin{'C'} += $tensu[2];
        $totalin{'D'} += $tensu[3];
        my @ptord; # 点数昇順リスト (last, pt4, pos3, pt3, .., top, pt1)
        foreach my $key (sort { $plpt{$a} <=> $plpt{$b} } keys %plpt) {
            push(@ptord, ($key, $plpt{$key}));
        }
        # ビンタ計算
        my @bbuff = (0, 0, 0, 0); # 点数やり取りバッファ
        # 4
        if ((($sowa == 100) && ($ptord[1] < 25)) ||
            (($sowa == 0) && (($ptord[1] + $umai2) < -5))) {
            # to 3
            if ((($sowa == 100) && ($ptord[3] < 25)) ||
                (($sowa == 0) && (($ptord[3] + $umai1) < -5))) {
                $bbuff[1] -= $binta; $bbuff[3] += $binta;
            } else {
                $bbuff[1] -= ($binta * 2); $bbuff[3] += ($binta * 2);
            }
            # to 2
            if ((($sowa == 100) && ($ptord[5] < 25)) ||
                (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
                $bbuff[1] -= $binta; $bbuff[5] += $binta;
            } else {
                $bbuff[1] -= ($binta * 2); $bbuff[5] += ($binta * 2);
            }
            # to 1
            if ((($sowa == 100) && ($ptord[7] < 25)) ||
                (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
                $bbuff[1] -= $binta; $bbuff[7] += $binta;
            } else {
                $bbuff[1] -= ($binta * 2); $bbuff[7] += ($binta * 2);
            }
        } else {
            # to 3
            $bbuff[1] -= $binta; $bbuff[3] += $binta;
            # to 2
            $bbuff[1] -= $binta; $bbuff[5] += $binta;
            # to 1
            $bbuff[1] -= $binta; $bbuff[7] += $binta;
        }
        # 3
        if ((($sowa == 100) && ($ptord[3] < 25)) ||
            (($sowa == 0) && (($ptord[3] + $umai1) < -5))) {
            # to 2
            if ((($sowa == 100) && ($ptord[5] < 25)) ||
                (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
                $bbuff[3] -= $binta; $bbuff[5] += $binta;
            } else {
                $bbuff[3] -= ($binta * 2); $bbuff[5] += ($binta * 2);
            }
            # to 1
            if ((($sowa == 100) && ($ptord[7] < 25)) ||
                (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
                $bbuff[3] -= $binta; $bbuff[7] += $binta;
            } else {
                $bbuff[3] -= ($binta * 2); $bbuff[7] += ($binta * 2);
            }
        } else {
            # to 2
            $bbuff[3] -= $binta; $bbuff[5] += $binta;
            # to 1
            $bbuff[3] -= $binta; $bbuff[7] += $binta;
        }
        # 2
        if ((($sowa == 100) && ($ptord[5] < 25)) ||
            (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
            # to 1
            if ((($sowa == 100) && ($ptord[7] < 25)) ||
                (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
                $bbuff[5] -= $binta; $bbuff[7] += $binta;
            } else {
                $bbuff[5] -= ($binta * 2); $bbuff[7] += ($binta * 2);
            }
        } else {
            # to 1
            $bbuff[5] -= $binta; $bbuff[7] += $binta;
        }
        # ポイント,ビンタ,レート計算
        # プレーヤ-点数 配列
        my %ptrslt = ($ptord[0]=>0, $ptord[2]=>0, $ptord[4]=>0, $ptord[6]=>0);
        # 4
        $ptord[1] -= $uma2; $ptord[1] -= $kaesi;
        $ptrslt{$ptord[0]} = $ptord[1];
        $ptord[1] = $ptord[1] * $krate; $ptord[1] += $bbuff[1];
        # 3
        $ptord[3] -= $uma1; $ptord[3] -= $kaesi;
        $ptrslt{$ptord[2]} = $ptord[3];
        $ptord[3] = $ptord[3] * $krate; $ptord[3] += $bbuff[3];
        # 2
        $ptord[5] += $uma1; $ptord[5] -= $kaesi;
        $ptrslt{$ptord[4]} = $ptord[5];
        $ptord[5] = $ptord[5] * $krate; $ptord[5] += $bbuff[5];
        # 1
        $ptord[7] += $uma2; $ptord[7] -= $kaesi; $ptord[7] += $oka;
        $ptrslt{$ptord[6]} = $ptord[7];
        $ptord[7] = $ptord[7] * $krate; $ptord[7] += $bbuff[7];
     
        # プレーヤ-金額 配列
        my %mnrslt = ($ptord[0] => $ptord[1], $ptord[2] => $ptord[3],
                      $ptord[4] => $ptord[5], $ptord[6] => $ptord[7]);
        printf("%+4d %+4d %+4d %+4d; ",
               $ptrslt{'A'}, $ptrslt{'B'}, $ptrslt{'C'}, $ptrslt{'D'});
        printf("%+6d %+6d %+6d %+6d;\n",
               $mnrslt{'A'}, $mnrslt{'B'}, $mnrslt{'C'},$mnrslt{'D'});
        foreach my $key (keys %ptrslt) {
            $totalpt{$key} += $ptrslt{$key}; $totalmn{$key} += $mnrslt{$key};
        }
    }
     
    # 総計出力
    print
        "----------------------------------------------------------------------\n";
    printf("%+4d %+4d %+4d %+4d; ",
           $totalin{'A'}, $totalin{'B'}, $totalin{'C'}, $totalin{'D'});
    printf("%+4d %+4d %+4d %+4d; ",
           $totalpt{'A'}, $totalpt{'B'}, $totalpt{'C'}, $totalpt{'D'});
    printf("%+6d %+6d %+6d %+6d;\n",
           $totalmn{'A'}, $totalmn{'B'}, $totalmn{'C'}, $totalmn{'D'});
     
    

    ※ 11.27 付記
    最初の版には,ウキの判定とビンタ計算のバグがありました。訂正しました。

    あと一点。同点者がいることを考慮してません。一局目の一巡目でいきなり役満放銃で一発ハコ一人沈みみたいな場合でない限り,こんなことはなかろうと思い,無視しました。

    ハガキ宛名差込印刷

    |

    あと少しすればもう年末。年賀状を書かなければならない時期が来る。住所録から LaTeX を用いてハガキ宛名印刷用 PDF を生成するツールを作ってみた。これは昔 UNIX 系の雑誌にあった記事の工夫なので,ネットで探せば同様のツールが見つかるかも知れないが,私自身の好みでレイアウトをカスタマイズできるよう,スクラッチから(すべてを一から)自作してみた。mkpostaddr-201111102344.tar.gz アーカイブを掲載しておくので,関心のある方はダウンロードしてお使いください。無保証です。

    処理方式は次のとおり。住所録 CSV ファイル(address.csv),LaTeX プリアンブル・ファイル(preamble.tex),ハガキ差込フォーム・ファイル(form.tex),ハガキレイアウト定義ファイル(post.conf)を読込んで,住所録の宛名の数だけハガキ宛名組版の LaTeX ソースコードを出力する。この際,日本語は OTF パッケージを用いて Adobe-Japan1-6 コードの広範囲の漢字を出力出来るようにする。この処理は Perl スクリプト mkpostaddr で行う。出来上がった TeX ファイルを eplatex 及び dvipdfmx で処理して PDF を生成する。これらのファイルはすべて UTF-8 エンコーディングでなければならない。サンプルの住所録 CSV ファイルで組版した結果 PDF 画像を以下に示す。
     

    20111110-hagaki.jpg
     

    住所録 CSV ファイルは,項目をカンマ区切りで記述した,いわゆる CSV ファイルである。このツールでは,1:宛名-姓, 2:宛名-名, 3:宛先郵便番号, 4:宛先住所(番地まで), 5:宛先住所(ビル,マンション情報), 6:会社名, 7:部署名, 8:宛名連名-姓, 9:宛名連名-名 の 9 項目からなるデータを前提としている。5 〜 9 は無ければ空データもしくは空白としておいてよいが,カンマは必ず全部で 8 必要である。Microsoft Excel などで住所録を作成し,区切り文字カンマ,文字コード UTF-8 指定で CSV 出力すればよい。このデータはハガキ差込フォーム・ファイルで記述された所定の変数位置に埋め込まれ,ハガキ宛名 LaTeX コードとなる。郵便番号の出力位置と差出人情報をハガキレイアウト定義ファイルに書いておく。郵便番号の出力位置については,日本郵便社お年玉年賀ハガキ向けにすでに調整してある。掲載ファイルにはサンプルが記述されているが,差出人は変更するか,不必要なら空にしておく。

    住所に英文字以外の外国語,たとえばロシア語を使いたい場合は,{\selectlanguage{russian}キリル文字} と書いておけばよい。両端のブレースは必須である。Babel パッケージをプリアンブル・ファイルに宣言してあり,仏・独・露語が使用可能である。もちろん利用者で追加・訂正してよい。

    少し長大になるが,各ファイル内容を掲載しておく。
     

    住所録 CSV ファイル(サンプル)

    あくまでサンプル。必ず,自分用に CSV 形式で作成してください。

    # -*- coding: utf-8; mode: text; -*-
    # 住所録CSVファイル(サンプル)
    # 1:宛名姓,2:宛名名,3:郵便番号,4:住所1,5:住所2(建物),
    #     6:会社名,7:部署名,8:宛名(連名)姓,9:宛名(連名)名
    \CID{13706}田,太郎,1020082,東京都千代田区一番町一—二—三,一番ビルヂング地下一階,株式会社 ヨシダ商事,営業部,,花子
    鈴木,花子,1020072,東京都千代田区飯田橋四—五—六,,,,,
    高橋,一郎,2100808,神奈川県川崎市川崎区旭町一—二三—四,大日本マンション一〇一号,,,,\hspace*{.5zh}桜\hspace*{.5zh}
    
     

    ハガキ差込フォーム・ファイル

    LaTeX コードにおいて住所録の数だけ繰返し出力される宛名印字頁整形用の雛形である。:数字: の部分が,住所録 CSV ファイルの情報で書換えられる。印字の位置,文字の大きさなど,気に入らなければ適宜修正ください。基本的には弄る必要はない。

    % -*- coding: utf-8; mode: latex; -*-
    % ハガキ差込フォーム・ファイル(TeX form 原稿)
    % $Id: form.tex 9 2011-11-10 13:41:03Z isao $
    % 2011 (c) isao yasuda.
    \begin{picture}(100,148)(3,3)
     
        %% 宛先郵便番号
        \Huge
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:1:}}%第1桁
        \advance\cntTOX by \cntTOW
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:2:}}%第2桁
        \advance\cntTOX by \cntTOW
        \put(\cntTOX,\cntTOY){\makebox[6mm][c]{:3:}}%第3桁
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:4:}}%第4桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:5:}}%第5桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:6:}}%第6桁
        \advance\cntTTX by \cntTTW
        \put(\cntTTX,\cntTTY){\makebox[6mm][c]{:7:}}%第7桁
     
        %% 住所
        \put(70,30){%
          \makebox(20,94)[rt]{%
          %\framebox(20,94)[rt]{%
            \begin{minipage}<t>[t]{94mm}
                \Large
                :8:\\%住所1
                \vspace*{1mm}%
                \hspace*{5mm}%
                :9:%住所2
            \end{minipage}
          }%
        }%
     
        %% 会社名・部書名・連名
        \put(45,30){%
          \makebox(30,88)[rt]{%
          %\framebox(30,88)[rt]{%
            \begin{minipage}<t>[t]{88mm}
                \hspace*{3mm}%
                \vspace*{-7mm}%
                {\large :10:\\}%会社名
                \hspace*{7mm}%
                \vspace*{-3mm}%
                {\large :11:\\}%部署名
                \vspace*{-3mm}%
                \def\lastname{:00:}%
                \settowidth{\namelen}{\lastname}\addtolength{\namelen}{1zw}%
                \makebox[\namelen][l]{\lastname}:01:~様\\%宛名
                %\hspace*{20mm}%
                \makebox[\namelen][l]{:12:}%
                :13:%連名
            \end{minipage}
          }%
        }%
     
        %% 差出人住所・氏名
        \put(10,32){%
          \makebox(30,58)[rt]{%
          %\framebox(30,58)[rt]{%
            \small
            \begin{minipage}<t>[t]{58mm}
              {\large :20:}\par%住所1
              \vspace*{3mm}%
              {\Large
              \hspace*{5mm}%
              \makebox[3.5zw][l]{:21:}\qquad :22:\\[2pt]%差出人姓名
              \hspace*{5mm}%
              \makebox[3.5zw][l]{:23:}\qquad :24:}\par%連名姓名
              \vspace*{3mm}%
              \hfill{\large :25:}%
            \end{minipage}
          }%
        }%
     
        %%% 差出人郵便番号
        \large
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:30:}}%第1桁
        \advance\cntFOX by \cntFOW
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:31:}}%第2桁
        \advance\cntFOX by \cntFOW
        \put(\cntFOX,\cntFOY){\makebox[4mm][c]{:32:}}%第3桁
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:33:}}%第4桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:34:}}%第5桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:35:}}%第6桁
        \advance\cntFTX by \cntFTW
        \put(\cntFTX,\cntFTY){\makebox[4mm][c]{:36:}}%第7桁
     
    \end{picture}
    
     

    ハガキレイアウト定義ファイル(サンプル)

    Perl プログラムの変数定義の形式で記述する。差出人情報は必ず修正しなければならない。宛名面に差出人情報を印字したくない場合は,ごっそり削除すればよい。郵便番号を出力する位置は,ミリ単位,整数で指定しないといけない。すでに日本郵便社お年玉年賀ハガキ向けに私が調整してあるので,修正する必要はないと思う。

    # -*- coding: utf-8; mode: cperl -*-
    # ハガキレイアウト定義ファイル
    # $Id: post.conf 9 2011-11-10 13:41:03Z isao $
    # 2011 (c) isao yasuda.
    # 宛先郵便番号
    $POSTNUMT1POSX =  46;  # 1桁目X位置
    $POSTNUMT1POSY = 131;  # 1-3桁目Y位置
    $POSTNUMT1WIDTH =  7;  # 1-3桁目送り幅
    $POSTNUMT2POSX =  67;  # 4桁目X位置
    $POSTNUMT2POSY = 131;  # 4-7桁目Y位置
    $POSTNUMT2WIDTH =  7;  # 4-7桁目送り幅
    # 差出人郵便番号
    $POSTNUMF1POSX =  10;  # 1桁目X位置
    $POSTNUMF1POSY =  26;  # 1-3桁目Y位置
    $POSTNUMF1WIDTH =  4;  # 1-3桁目送り幅
    $POSTNUMF2POSX =  23;  # 4桁目X位置
    $POSTNUMF2POSY =  26;  # 4-7桁目Y位置
    $POSTNUMF2WIDTH =  4;  # 4-7桁目送り幅
    # 差出人
    $FROMADR = '横浜市港北区綱島一—二—三'; # 住所
    $FROMPCD = '1234567';   # 郵便番号
    $FROMLN1 = '田 中';     # 姓
    $FROMFN1 = '達夫・律子';  # 名
    $FROMLN2 = ' ';        # 連名・性
    $FROMFN2 = '一郎・次子'; # 連名・名
    $FROMOTH = '012-345-6789'; # その他(下寄せ)
    
     

    LaTeX プリアンブル・ファイル

    生成されるハガキ宛名印刷 LaTeX コードの先頭に出力されるコードである。もしフォントなどを変更したい場合は,パッケージ,デフォルト・ローマン書体の指定を追加すればよい。Babel パッケージが指定されているが,外国語が不要なら削ってもよい。

    % -*- coding: utf-8; mode: latex; -*-
    % LaTeX プリアンブル・ファイル(TeX preamble 原稿)
    % $Id: preamble.tex 6 2011-11-10 01:44:50Z isao $
    % 2011 (c) isao yasuda.
    \documentclass{jsarticle}
    \special{papersize=100mm,148mm}%はがき大きさページサイズ
    \usepackage[T2A,T1]{fontenc}%
    \usepackage[utf8x]{inputenc}%
    \usepackage{plext}%pTeX縦組ツール
    \usepackage[russian,english,french,german,japanese]{babel}%多国語
    \usepackage[deluxe,expert,multi]{otf}%
    \setlength{\textwidth}{100mm}%ハガキ幅
    \setlength{\textheight}{148mm}%ハガキ高
    \setlength{\oddsidemargin}{-25.4mm}%左横マージン0
    \setlength{\topmargin}{-25.4mm}%上マージン0
    \setlength{\headheight}{0mm}%
    \setlength{\headsep}{0mm}%
    \setlength{\unitlength}{1mm}%picture環境単位mm
    \pagestyle{empty}%ページ番号なし
    \newlength{\namelen}%宛名幅用
    \parindent=0pt\relax
    
     

    mkpostaddr はがき印刷用住所差込 Perl コード

    Perl プログラム。現在の Perl 処理系ならモジュール追加なしに実行できるはずである。よほど印刷様式が気に入らない限り,修正は不要のはずである。

    #!/usr/bin/perl
    # -*- coding: utf-8; mode: cperl; -*-
    #
    #  mkpostaddr はがき印刷用住所差込
    #  $Id: mkpostaddr 9 2011-11-10 13:41:03Z isao $
    #  2011 (C) isao yasuda, All Rights Reserved.
    #
    #  SUMMARY
    #  ------
    #  アドレスCSVファイルを読込み,はがき宛名印刷用TeXファイルを生成する
    #
    #  DESCRIPTION
    #  -----------
    #  1. preamble ファイルを読込み,出力する
    #  2. コンフィグファイルを読込み,TeX 形式で出力する
    #  3. 明細フォームファイルを読込み,form 配列に行展開する
    #  4. アドレスcsvファイルを一行ずつ読込み,form パターンに差込み,出力する
    #
    #  USAGE
    #  -----
    #  1. address.csv, post.conf, preamble.tex, form.tex は UTF-8 であること
    #  2. コマンドラインは以下のとおり
    #     % mkpostaddr -c post.conf -f form.tex -p preamble.tex \
    #       < address.csv > hagaki.tex
    #     % eplatex hagaki.tex
    #     % dvipdfmx hagaki.dvi
    #
    use strict;
    use utf8;
    use Getopt::Std;
    use File::Basename;
     
    binmode(STDOUT, ":utf8");
    # コマンドライン引数処理
    my %opts = (
                'c' => 'post.conf',    # configuretion
                'f' => 'form.tex',     # form.tex
                'p' => 'preamble.tex'  # preamble.tex
               );
    Getopt::Std::getopts('c:f:p:', \%opts) || usage();
     
    # プリアンブル出力
    open(INP, "<:utf8", $opts{'p'}) ||
        die $opts{'p'} . " file not found.\n";
    print $_ while (<INP>); close(INP);
     
    # コンフィグ初期値 単位 mm
    # 宛先郵便番号
    my $POSTNUMT1POSX =  48; # 1桁目X位置
    my $POSTNUMT1POSY = 132; # 1-3桁目Y位置
    my $POSTNUMT1WIDTH =  6; # 1-3桁目送り幅
    my $POSTNUMT2POSX =  68; # 4桁目X位置
    my $POSTNUMT2POSY = 132; # 4-7桁目Y位置
    my $POSTNUMT2WIDTH =  6; # 4-7桁目送り幅
    # 差出人郵便番号
    my $POSTNUMF1POSX =  10; # 1桁目X位置
    my $POSTNUMF1POSY =  16; # 1-3桁目Y位置
    my $POSTNUMF1WIDTH =  4; # 1-3桁目送り幅
    my $POSTNUMF2POSX =  24; # 4桁目X位置
    my $POSTNUMF2POSY =  16; # 4-7桁目Y位置
    my $POSTNUMF2WIDTH =  4; # 4-7桁目送り幅
    # 差出人
    my $FROMADR = ''; # 住所
    my $FROMPCD = ''; # 郵便番号
    my $FROMLN1 = ''; # 姓
    my $FROMFN1 = ''; # 名
    my $FROMLN2 = ''; # 連名・性
    my $FROMFN2 = ''; # 連名・名
    my $FROMOTH = ''; # その他(下寄せ)
     
    # コンフィグ定義読込
    open(INC, "<:utf8", $opts{'c'}) ||
        die $opts{'c'} . " file not found.\n";
    eval($_) while (<INC>); close(INC);
     
    print <<"EOM";
    \\newcount\\cntTOX \\cntTOX=$POSTNUMT1POSX
    \\newcount\\cntTOY \\cntTOY=$POSTNUMT1POSY
    \\newcount\\cntTOW \\cntTOW=$POSTNUMT1WIDTH
    \\newcount\\cntTTX \\cntTTX=$POSTNUMT2POSX
    \\newcount\\cntTTY \\cntTTY=$POSTNUMT2POSY
    \\newcount\\cntTTW \\cntTTW=$POSTNUMT2WIDTH
    \\newcount\\cntFOX \\cntFOX=$POSTNUMF1POSX
    \\newcount\\cntFOY \\cntFOY=$POSTNUMF1POSY
    \\newcount\\cntFOW \\cntFOW=$POSTNUMF1WIDTH
    \\newcount\\cntFTX \\cntFTX=$POSTNUMF2POSX
    \\newcount\\cntFTY \\cntFTY=$POSTNUMF2POSY
    \\newcount\\cntFTW \\cntFTW=$POSTNUMF2WIDTH
    \\begin{document}
    EOM
     
    my @frompcd = ("", "", "", "", "", "", "");
    if ($FROMPCD) {
        @frompcd = split(//, $FROMPCD);
    }
     
    # フォーム入力/行展開
    open(INF, "<:utf8", $opts{'f'}) ||
        die $opts{'f'} . " file not found.\n";
    my @form = <INF>; close(INF);
     
    # 住所CSVファイルを読み込み,行毎にはがき整形する
    while (<STDIN>) {
        utf8::decode($_);
        # コメント行(\#),空行は読み飛ばす
        next if /^#/; next if /^\s*$/; chomp;
        my @addr = split(/,/, $_);
        next if ($addr[0] eq "");
        my @postnum = split(//, $addr[2]);
        my @formwk = @form;
        foreach my $corln (@formwk) {
            # 宛名1姓00
            $corln =~ s/:00:/$addr[0]/;
            # 宛名1名01
            $corln =~ s/:01:/$addr[1]/;
            # 郵便番号 1-7
            for (my $i = 1; $i <= 7; $i++) {
                $corln =~ s/:$i:/$postnum[$i-1]/;
            }
            # 住所1: 番地まで
            $corln =~ s/:8:/$addr[3]/;
            # 住所2: ビル・アパート・部屋
            $corln =~ s/:9:/$addr[4]/;
            # 会社名
            $corln =~ s/:10:/$addr[5]/;
            # 部署名
            $corln =~ s/:11:/$addr[6]/;
            # 宛名2連名姓
            $corln =~ s/:12:/$addr[7]/;
            # 宛名2連名名
            my $ren = $addr[8];
            $ren .= "~様" if ($ren);
            $corln =~ s/:13:/$ren/;
            # 差出人
            $corln =~ s/:20:/$FROMADR/;
            $corln =~ s/:21:/$FROMLN1/;
            $corln =~ s/:22:/$FROMFN1/;
            $corln =~ s/:23:/$FROMLN2/;
            $corln =~ s/:24:/$FROMFN2/;
            for (my $i = 30; $i <= 36; $i++) {
                $corln =~ s/:$i:/$frompcd[$i-30]/;
            }
            $corln =~ s/:25:/$FROMOTH/;
            print $corln;
        }
        print "\\newpage\n";
    }
    print "\\end{document}\n";
     
    sub usage {
        my $prog = basename($0);
        die <<"EOM";
    Usage: $prog -c config-file -f form-file -p preamble-file
           \< address-csv-file \> postcard.tex
      -c   configuration file (default: post.conf)
      -f   form TeX file with variables (default: form.tex)
      -p   preamble definition file (default: preamble.tex)
    EOM
    }
    
     

    掲載したアーカイブには Makefile を添付しており,UNIX 系ユーザなら make とすればサンプル PDF が生成される。dvipdfmx OTF パッケージ用マップファイル otf-ptex-noembed.map も添付してあるが,これは日本語フォントを埋込まない。フォント埋込み PDF を生成したい場合は,ユーザで修正してほしい。コマンドラインを手入力する場合は,以下のとおりとすればよい。

    % ./mkpostaddr -c post.conf -f form.tex -p preamble.tex < address.csv > hagaki.tex
    % eplatex hagaki.tex
    % dvipdfmx -f otf-ptex-noembed.map hagaki.dvi
    

    宛名情報の出力様式(フォント,空き,出力位置)などをカスタマイズしたい場合は,form.tex を確認して,ご自分用に修正していただきたい。

    しばらく前に,会社の品質保証部のセキュリティ情報で Apache Web サーバの DoS 脆弱性が話題になり,現場に対し担当システムを確認するよう指示がまわっていた。担当顧客システムの調査/対応が一段落したので,自宅の Web サーバもチェックしてみることにした。

    この脆弱性のアドヴァイザリは「JVNTR-2011-05 Apache HTTPD サーバにサービス運用妨害 (DoS) の脆弱性 (CVE-2011-3192, JVNVU#405811)」。Apache 2.x 系すべてが対象とのこと。Range ヘッダ処理で Web サーバのみならずシステム全体のサービス不能を引き起こす問題である。

    自宅の FreeBSD 8.2-RELEASE 公開 Web サーバを確認すると,インストールした Apache22 の CHANGES(修正履歴ファイル)には Fix a regression introduced by the CVE-2011-3192 byterange fix in 2.2.20. とあるので,すでに対策済みの版だとわかった。OS を組込んだ 9 月には,FreeBSD Apache22 ports は対策版に更新されていたわけである。この問題を検証できる Perl コード Apache Killer - killapache.pl が公開されている。検証のため,公開サーバをこのプログラムで攻撃してみた。

    isolde:/Users/i-yasuda/var/ % perl killapache.pl beatrice
    Host does not seem vulnerable
    

    となって,確かに対策済みだとわかった。killapache.pl 攻撃コードは Perl IO::Socket 及び Parallel::ForkManager モジュールを必要とするのであらかじめ組込んでおく必要がある。ためしに,メインで使っている書斎の Mac OS X Tiger マシンを攻撃してみると,みるみるうちに CPU 利用率が 100% に跳ね上がってそれが延々と続き,操作がずしんと重くなった。

    この脆弱性は有名だし,攻撃コードが公開されているくらいだから,ご自分で Web サーバを運用されている方はぜひチェックして,未対応なら Apache を最新版に更新していただきたい。インストールした Apache のドキュメントを見て,CVE-2011-3192 アドヴァイザリが対策されていればよい。

    デニス・リッチー死去

    |

    先日スティーブ・ジョブズの死去がトップニュースで報道され,大きな話題になった。確かに,アップルの今後の動向は IT 業界に大きな影響を与えるので,当然の扱いである。今日,デニス・リッチー死去のニュースがひっそりと掲載されていた

    でも,私のような 1980 年代に計算機の素養を身に付けた者にとっては,デニス・リッチーのほうがスティーブ・ジョブズよりも,遥かに,何倍も,圧倒的に,ネームバリューが高いのではないだろうか。ブライアン・カーニハン,ケン・トンプソンなどとともに,デニス・リッチーの業績は,Programming language C,UNIX という,現代の計算機基盤の創造と直接に結びついているからである。スティーブ・ジョブズやビル・ゲイツが「ビジネス」にして大成功した,その礎を築いたのが彼らだからである。

    私にとって,デニス・リッチーは計算機「文化」の生けるレジェンドであった。彼の名著『プログラミング言語 C』に,どれだけ多くの人が C の文法のみならず「プログラミング書法」というものを教えられただろうか。私はこの本を読んで,プログラミングは間違いなく文化表現たりうるという確信をもったものだった(この本についてはここに記した)。そのレジェンドがとうとう歴史のなかに移行しようとしている。私には,敬愛する大作家が亡くなったような,そんな感慨がある。

    ruby マークアップ・ツール

    |

    昨日書いた泉鏡花『貧民倶樂部』の記事において,鏡花テクスト引用のために大量のルビをマークアップしなければならなかった。HTML で <ruby><rb></rb><rp></rp><rt>これ</rt><rp>)</rp></ruby> とマークアップすると,これ となる。<ruby> タグをサポートしていないブラウザならタグ以外のテキストがそのまま出力されて 此(これ)となる。LaTeX にも奥村先生のマクロ集に \ruby 命令があり,この場合は \ruby{此}{これ} とマークアップする。これらのルビ付加用マークアップ作業は,やっている最中に元の文章が分断されて判りにくくなりかつ極めて面倒なので,私は自分で書いた簡単なツールで一括整形している。今日はその整形ツール convertruby を紹介しておく。私は Mac OS X で使用しているが,FreeBSD, Linux でも動作するはずである。同じ必要に迫られた方はプログラム・コードをコピってお使いください。

    このツールは,標準入力から UTF-8 テキストを読み,此(これ)のように,漢字 + + 読み + のように書いた文字列に対して,マークアップを行う。デフォルトでは HTML タグを付加する。-l オプションを指定すると LaTeX 形式で整形する。対象テキストの括弧は全角でないといけない。そういう融通の利かないところがある。開括弧より前のテキストを後ろから逆方向に走査し,漢字以外が出現するまで漢字をスタックにプッシュし,あとでこのスタックからポップして得られる文字列(先入れ後出しにより文字の順番が戻る)を親文字と判定する。「漢字」は Unicode CJK 統合漢字に属するかを \p{InCJKUnifiedIdeographs} 正規表現文字クラスで判断している。Perl コードは以下の通り。

    #!/usr/bin/perl -w
    # -*- coding: utf-8; mode: cperl; -*-
    #
    #     convertruby: ルビ・マークアップ
    #     - ベタのテキストを <ruby> or \ruby シーケンスに整形する
    #     - 例: 此(これ) ["(", ")" は全角括弧]
    #       -> <ruby><rb>此</rb><rp>(</rp><rt>これ</rt><rp>)</rp></ruby> (default)
    #       -> \ruby{此}{これ} (with -l option)
    #     - usage: convertruby [-l] < stdin
    #          -l: for LaTeX; default: for HTML
    #
    use strict;
    use utf8;
    use Getopt::Std;
    use File::Basename;
    binmode(STDOUT, ":utf8");
    # コマンドライン・オプション処理
    my %opts = ('l' => 0);
    Getopt::Std::getopts('l', \%opts) ||
        die "Usage: " . basename($0) .
        " [-l] \< (stdin)\n   -l: for LaTeX; default: for HTML\n";
    # 行毎の主処理
    while (<STDIN>) {
        my $line = $_;
        utf8::decode($line); utf8::decode($_);
        my ($yomi, $leftt, $rightt);
        # 「(読み)」を含まない行はそのまま出力
        unless ($_ =~ /([^)]*)/gi) {
            print $line; next;
        }
        # 「(読み)」を走査し,これがある限りその前後を切り出す
        while ($line =~ /([^)]*)/gi) {
            $yomi = $&; $leftt = $`; $rightt = $';
            # 「(読み)」から括弧を外す
            $yomi =~ s|[()]||g;
            # 対象漢字部を後方から走査し,漢字スタックに push する
            my @kstack = (); my $k;
            my @istack = split(//, $leftt); # 「読み」の左側テキストの文字配列
            while ((@istack) &&
                   (($k = pop(@istack)) =~ /\p{InCJKUnifiedIdeographs}/g)) {
                push(@kstack, $k); $k = "";
            }
            # 最後に pop した非漢字を左側テキスト配列スタックに戻しておく
            push(@istack, $k) if ($k);
            # 漢字スタックから漢字部文字列を pop で復元
            my $kanji = "";
            while (@kstack) {
                $kanji .= pop(@kstack);
            }
            # 抽出した漢字部よりも前の文字列を出力
            if (@istack) {
                print $_ for @istack;
            }
            # ruby マークアップ部分を出力
            if ($kanji) {
                if ($opts{'l'}) {
                    # LaTeX (with -l option)
                    print '\ruby{' . $kanji . '}{' . $yomi . '}';
                } else {
                    # HTML (default)
                    print '<ruby><rb>' . $kanji .
                        '</rb><rp>(</rp><rt>' . $yomi . '</rt><rp>)</rp></ruby>';
                }
            } else {
                # 「漢字(読み)」のパターンでないものはそのまま出力
                print "($yomi)";
            }
            # 走査対象文字列に右側テキスト(残り)文字列を格納して,繰り返し
            $line = $rightt;
        }
        # 残り文字列を出力
        print $rightt if ($rightt);
    }
    
    GNU Emacs からは,以下を .emacs に追加すれば,利用できるようになる。ここでは convertruby/usr/local/bin 下にインストールされているものとしている。この定義を Emacs に評価させた後,テキスト・リージョンを指定し,M-x convert-ruby-html, M-x convert-ruby-latex を実行すると,当該リージョンがそれぞれ HTML, LaTeX のルビ・マークアップ整形を施される。

    ;; convertruby for HTML
    (defun convert-ruby-html (start end)
      "Convert text to ruby HTML sequence."
      (interactive "r")
      (call-process-region
       start end
       "/usr/local/bin/convertruby"
       t (list t nil) nil
       "")
      )
    ;; convertruby for LaTeX
    (defun convert-ruby-latex (start end)
      "Convert text to ruby HTML sequence."
      (interactive "r")
      (call-process-region
       start end
       "/usr/local/bin/convertruby"
       t (list t nil) nil
       "-l")
      )
    

    泉鏡花『貧民倶樂部』の引用に際しては,新字・新仮名遣いで引用文を作成し(図 1),自作のソフトで旧字・旧仮名遣いに変換し,次に,ルビを付けたい漢字に「(よみ)」を付加し(図 2),最後に Emacs 上で convertruby を実行して <ruby> タグをマークアップした(図 3)。ただし,図 3-1 は HTML 整形の,図 3-2 は LaTeX 整形の結果例である。
     

    20111012-ruby-1.jpg図 1. ルビなしで新字・新仮名遣いで記述
    20111012-ruby-2.jpg図 2. 表記変換後,ルビを付加
    20111012-ruby-3.jpg図 3-1. HTML 整形実行結果
    20111012-ruby-4.jpg図 3-2. LaTeX 整形実行結果

    misima 漢詩作成支援: 平仄音韻分析・詩語検索 misimakansiservlet を使っている方からメールをいただいた。そこで思い出したように,久しぶりに辞書のメンテナンスを行った。平仄などのデータを格納した辞書=漢字データベースは SQLite3 で作成したのだけど,SQLite3 オペレーションを中心とするメンテナンス手順を忘れてしまっていた。思い出したのを整理し,ここにも備忘録を残しておく。以下,beatirce:/home/isao[nnnn] % は misima サーバ機端末の tcsh プロンプト,nnnn はコマンド履歴番号を示す。

    misimakansiservlet において,漢詩分析デーモン misimakansiserver は漢字データベース KANJITBL で見つからなかった文字をログに出力するようになっている。私はこれを定期的にチェックし,それらの辞書にない文字(ユーザが入力したが辞書になかった漢字)を手作業で DB に追加する。ログのその部分を抽出する。

    beatirce:/home/isao[1000] % grep -e '\*\*\*' /var/log/kansiserver.log | sort | uniq
    ***** 㱕 が KANJITBL にありません.
    ***** 僲 が KANJITBL にありません.
    ***** 嵆 が KANJITBL にありません.
    ***** 扃 が KANJITBL にありません.
    ***** 涴 が KANJITBL にありません.
    ***** 疴 が KANJITBL にありません.
    ***** 裛 が KANJITBL にありません.
    ***** 髥 が KANJITBL にありません.
    beatirce:/home/isao[1001] % 
    

    というわけで,今回この 8 文字を追加。漢字字典を調べ,平仄,読み,語義などを DB のソースコード kanji.src にテキストエディタで追加する。完了したら,create table で漢字データベースファイル KANJI.db を生成する。そしてこれに kanji.src の内容を .import コマンドでロードする。kanji.src のデータ記述はカンマ区切りの CSV 形式なので,ロード実行には -separator , オプションを指定している。

    beatirce:/home/isao[1001] % cd misima
    beatirce:/home/isao/misima[1002] % sqlite3 KANJI.db < mkkanji.sql
    beatirce:/home/isao/misima[1003] % sqlite3 -separator , KANJI.db ".import kanji.src KANJITBL"
    beatirce:/home/isao/misima[1004] % 
    

    ここで,mkkanji.sql とは create 文で DB のスキーマ(データ項目とその属性)を定義する元ネタである。misimakansiservlet の場合は以下のようなものである。

    -- -*- coding: utf-8; mode: sql; -*-
    -- misimakansiservert 平仄音韻分析用漢字テーブルレイアウト
    -- 漢字 ji, 平仄 hs, 韻 in1, 韻 in2, 音読 yo, 訓読 yk, 備考 bk
    create table KANJITBL (
           ji text not null,
           hs text not null,
           in1 text not null,
           in2 text not null,
           yo text not null,
           yk text not null,
           bk text not null
    );
    

    これで漢字データベースはできた。データ内容を確認して問題なければ,/usr/local/etc/misima/ の下にコピーする。これで Web サーブレットから使えるようになる。以下,SQLite3 DB 操作のメモ。

    1. SQLite3 コマンドラインの起動
    2. 扱う DB を引数に指定する。
      beatrice:/home/isao/misima[1004] % sqlite3 KANJI.db
      SQLite version 3.7.7.1 2011-06-28 17:39:05
      Enter ".help" for instructions
      Enter SQL statements terminated with a ";"
      sqlite> 
      
    3. テーブル名の表示
    4. sqlite> .table
      KANJITBL
      
    5. DB スキーマの表示
    6. sqlite> .schema KANJITBL
      CREATE TABLE KANJITBL (
                 ji text not null,
                 hs text not null,
                 in1 text not null,
                 in2 text not null,
                 yo text not null,
                 yk text not null,
                 bk text not null
      );
      
    7. DB 件数の確認
    8. sqlite> select count(*) from KANJITBL;
      6929
      
    9. tcsh コマンドラインから直接 SQL を入力する
    10. sqlite3 [opt] [db-file] に続けてクオート(ダブルクオーテーションマークで括る)して SQL 文を書けばよい。SQLite3 は標準入力からも SQL を受け付けるので,テキストファイルに書いておきリダイレクトで読み込ませてもよい。
      beatrice:/home/isao/misima[1005] % sqlite3 KANJI.db \
      "select * from KANJITBL where ji='髥';"
      髥|◎|hs:14:塩|sk:29:艷|ぜん;ねん|-|ほおひげ;鬚髯:柔らかいあごひげとほおひげ;
      beatrice:/home/isao/misima[1006] % cat sql.txt
      select * from KANJITBL where ji='㱕';
      select * from KANJITBL where ji='僲';
      select * from KANJITBL where ji='嵆';
      select * from KANJITBL where ji='扃';
      select * from KANJITBL where ji='涴';
      select * from KANJITBL where ji='疴';
      select * from KANJITBL where ji='裛';
      select * from KANJITBL where ji='髥';
      beatrice:/home/isao/misima[1007] % sqlite3 KANJI.db < sql.txt
      㱕|○|hk:05:微|-|き|かえる;かえす|帰・歸の異体字;
      僲|○|hs:01:先|-|せん|-|仙人;僊の異体字;
      嵆|○|hk:08:齊|-|けい;げ|-|稽古の当て・古いことを集めて考える;嵆山:山の名;嵆康:姓の一;
      扃|○|hs:09:青|-|けい;きょう|-|かんぬき;とざす;
      涴|●|sj:13:阮|sk:21:箇|えん;おん;わ|-|涴演:川が曲がりまわって流れるさま;けがす・けがれる;
      疴|◎|hs:05:歌|sk:22:禡|あ;か;け|-|やまい;痾に同じ・痾疾;
      裛|●|sn:14:緝|sn:16:葉|ゆう;おう;よう|-|つつむ・香がまとわりつく;書物の覆い;しっとりと濡れる;衣の部;
      髥|◎|hs:14:塩|sk:29:艷|ぜん;ねん|-|ほおひげ;鬚髯:柔らかいあごひげとほおひげ;
      beatrice:/home/isao/misima[1008] % 
      

    これで追加した漢字の平仄分析ができるようになった。
     

    20111008-kanjituika.jpg

    ジョブズ氏逝く・小沢裁判

    |

    今日の朝日夕刊の一面は,「小沢裁判・全面否認」と「アップル創業者スティーブ・ジョブズ氏死去」の記事だった。

    * * *

    スティーブ・ジョブズは,マイクロソフトのビル・ゲイツと並んで,パーソナル・コンピュータ業界の偉大なカリスマである。ゼロックス・パロアルト研究所やベル研究所,カリフォルニア大学バークレー,マサチューセッツ工科大学の研究成果をいただくことと,M & A による領土拡張とによる商売の大成功。お互いに憎み合いしのぎを削るマイクロソフトとアップルはどちらもこうした共通点がある。彼らの実業家センスは現代アメリカン・ドリームの象徴である。Windows は,コンピュータについては何も知らない人が使うパソコンに,頼まなくても搭載されているオペレーティング・システムとして世界を席巻している。一方,ジョブズのアップル・マッキントッシュにはアンチ Windows の熱狂的なファンがいる。かつて,ド素人向け「PC」(IBM-PC/AT の略)用という Microsoft Windows のイメージに対し,Apple Mac OS は QuarkXPress 全盛を受けて DTP オペレータやデザイナといったプロフェショナル・クリエイタ御用達の「おしゃれ」なイメージがあった。「少数派」ってわけである。ご存知の通りアップルはその後 iPod を世に出して,ソニーのウォークマンを駆逐し,レコード業界を窮地に陥れた。コンピュータが音楽を滅ぼす時代を作った。社名の示す通り,禁断の林檎を食わせた。いいことである。

    私も Mac を使っているけれども,「おしゃれ」を自称する一般のマックファンのような思い入れはまったくない。Windows も会社でありがたく使わせてもらっている。品質では悪名高い Windows 以上にバグが多い,そしてなかなか訂正されない,というのが,私の Mac に対する不満である(アップルがこれであんまり叩かれないのは,「少数派」の熱狂的サポータに支えられているからである)。UNIX として使用できなおかつ Adobe の Photoshop, Illustrator が動くマシン,— もっと正確にいうと — LaTeX でヒラギノフォントが使えるマシン,ということが,私の Mac を使う一番の,— もっと正確にいうと,唯一の — 理由である。ん,ジョブズ氏死去と何の関係があるって? 私とジョブズ氏とは,本来,何の関係もありません。

    * * *

    小沢さんの裁判は,検察審査会(=「正義感」だけの何もわからない素人)が密室で決めた「強制起訴」決議に基づく(ということは,検察がこれら 11 人の怒れるアマチュア集団を言葉巧みに誘導したんでしょうな)。先日彼の子分たちの一審有罪が宣告されたばかりである。この二つの「裁判」はいったい何の何処に犯罪性があるのかさっぱりわからない — だから「説明責任」で騒がしい — のだけれど,世の中は「金権巨悪を裁け」のような扱いである。

    小沢さんは,「僕が悪かった」以外は何を言おうが「説明」と捉えてもらえない「説明責任」という,メビウスの輪のように循環する衆愚的虐待に苛まれている。朝日は書いている:「小沢氏が起訴された背景には,説明不足に対する国民の不信感があるのは間違いない」。でも,身に覚えのない罪を着せられた人は,普通,「説明」のしようがないのではあるまいか。この記事が述べていることは,すなわち,日本という国は不信感で人が起訴されうる国だということである。これを改めて認識しよう。収賄の事実がまったく出ていないのにもかかわらず,生身の人間(たとえ政治家であろうと)を「説明不足に対する国民の不信感」ゆえに被告席に立たせているこの異常な事態を,朝日はなぜ問題視しないのか? この信じられない立件と強制起訴のおかげで,関係者は皆,三流週刊誌やバカ・ネット住民によって,普通の精神の持ち主なら死にたくなるであろうほどに,誹謗中傷され,人格を否定されているのである。

    マスコミのレベルは国民の品性を象徴するものだと私は思っている。いま私は自称先進国・日本という国がまったく理解できない。

    自宅サーバ復旧

    |

    土曜日,自宅のサーバが故障した。NFS マウントしたビデオデータを Mac OS から観ていると頻繁に停止する。挙げ句,サーバ接続が切断される。FreeBSD サーバのログを確認すると,LBA read に失敗した旨のエラーメッセージが大量に出力されている。南無三宝,ディスクがぶっ飛んだらしい。サーバをリブートしたところ,ハードウェアの probe のあと fsck で回復不能エラーを検知して起動が停止してしまった。「エッチな映画ばかり観てるからバチが当たったのよ」とは妻。正しい。教訓その1:NFS サーバにエッチな映画を入れて楽しむのはやめよう。

    ディスクを買って来て,交換し,FreeBSD をインストールし,Apache やらなにやら大量のプログラムをインストールし,サーバ環境を整え,... と考えると気が遠くなった。しかしメールサーバが使えなくなるとやっかいか,ブログもパーか,などなど考えるにつけ,しようがないなーとサーバ復旧に着手したのである。おかげでこの連休はほとんどパーになってしまった。

    日曜日,娘の学園祭を見に行ったあと,川崎駅前ヨドバシカメラで Hitachi Global Storage Technology 社製の SATA2 7200 rpm 1TB HDD バルク品を 5,000 円足らずで購入。帰宅して,サーバ筐体を開けて HDD を交換しようとしたら,なんと壊れた既設の HDD が取り外せない。このベアボーンは,HDD を装着したネジを覆うように,特殊な方法で電源を固定していたのである。「じゃ増設」と考えたんだけど,予備の SATA 用電源コネクタがひとつも空きがない。しかも,SATA 用ケーブルもない。私の知らないうちに(PC を自分で組立てるなんてもう 15 年くらいやっていない) PC の世界も当然ながら様変わりしているわけで,内蔵 HDD の電源コネクタはいまや 4pin ならず,マザーボードとの接続も IDE ケーブルならぬ SATA ケーブルなんである。昔悩んだ IDE プライマリ/セカンダリのジッパ設定から解放されたのは嬉しかったんだけど。ふたたびヨドバシカメラに逆戻りして,4pin-SATA 電源変換ケーブル,SATA ケーブルを追加購入しなければならなかった。教訓その2:内臓周辺機器を増設するときは,あらかじめ筐体内部を確認し,追加の必要なケーブル類を洗い出しておこう。

    20110920-pcsata.jpg
    4pin-SATA 電源変換ケーブル(上)/SATA コネクタ(下)

    取得してあったシステムのバックアップは何ヶ月か前のものであり,ブログをはじめ日々更新されるデータは最新状態ではない。教訓その3:バックアップはこまめに取得し DVD-R などに焼いておこう。ディスクを「増設」するついでに,壊れた HDD 内のまだ生きているデータを漁って,使えるものは使おうということにした。今日はその苦肉の方法のメモを残しておく。

    まずは SATA のプライマリソケットに再度旧 HDD をつないでブートする。当然,fsck で悲鳴を上げて,次のメッセージとともに停止する。

    Automatic file system check failed; help!
    ERROR: ABORTING BOOT (sending SIGTERM to parent)!
    Sep 18 16:03:45 init: /bin/sh on /etc/rc terminated abnormally, going to user mode
    Enter full pathname of shell or RETURN for /bin/sh:
    

    help! は機械のお前じゃなくこっちのセリフじゃ! これに対し,Enter キーを押して,シングルユーザモードでログインする。自作プログラム,文書,ブログ記事など大事なデータを格納しているディスク・パーティションをマウントしてみる。/etc/fstab を参照し,デバイス名とマウンティングポイントを特定する。これらは利用者のインストールの仕方によって変わるので注意。

    # cat /etc/fstab
    # Device  Mountpoint FStype Options Dump Pass#
    /dev/ad4s1b  none    swap   sw      0    0
    /dev/ad4s1a  /       ufs    rw      1    1
    /dev/ad4s1g  /home   ufs    rw      2    2
    /dev/ad4s1h  /shared ufs    rw      2    2
    /dev/ad4s1f  /tmp    ufs    rw      2    2
    /dev/ad4s1d  /usr    ufs    rw      2    2
    /dev/ad4s1e  /var    ufs    rw      2    2
    /dev/acd0 /cdrom  cd9660 ro,noauto  0    0
    linprocfs /compat/linux/proc linprocfs rw 0 0
    

    私の目的とするデータは /usr/home にあるので,この二つだけをもう一度 fsck でチェックし,そのあとで mount してみる。幸いにもこれらパーティションは無事だったようである。vi などのツールは /usr/bin にあるので,それらツールを使う場合もマウントしておく必要がある。

    # fsck -y /dev/ad4s1d
    # fsck -y /dev/ad4s1g
    # /sbin/mount /dev/ad4s1d /usr
    # /sbin/mount /dev/ad4s1g /home
    

    必要なデータをアーカイブする(ここでは省略)。上記マウントができれば当該ファイルシステムは書き込みも可能なはずである。ルートパーティションはおそらく書き込み不可なので,アーカイブはマウントしたファイルシステムに格納する。アーカイブデータを別のコンピュータにコピーするには,ネットワークが使えないといけない。この時点ではネットワーク通信の準備ができていないので,次にこれを行い,取得したデータを scp (ftp でもよい) で別マシンに転送する。以下では anotherpc のユーザ user のホームディレクトリにコピーしている。anotherpc 上でも ssh が動作可能でなければならない。Mac OS など UNIX 系 OS なら OK である。

    # ifconfig msk0 inet 192.168.1.4 netmask 255.255.255.0
    # scp mydata-archives.tar.gz user@anotherpc:/Users/user/
    

    msk0 はネットワークインタフェース名であり,利用する LAN カードによって変わって来る。忘れてしまっていたら,/etc/rc.conf 中に ifconfig_XXX=... なる行があるはずなので,その XXX をネットワークインタフェース名に指定する。IP アドレスは使っていたものをそのまま使うのがよい。詳細は ifconfig のマニュアルを参照。

    必要なデータをすべて別マシンに転送し終わったら,ほっと一息。煙草で一服し,コーヒーでも飲もう。シャットダウンし,電源コードを抜く。新しく追加した HDD の SATA ケーブルをプライマリに,旧い HDD のケーブルを DVD ドライブの次のソケットあたりに繋ぎ直す。ついでに筐体内部に溜まりまくったホコリをエアスプレーで吹き払っておく。とくに冷却ファンの近辺を念入りに。これで PC の騒音がピタリと止む。

    ここからは新しい HDD に最新バージョンの FreeBSD を導入する作業となる。私は Mac で,FreeBSD 8.2-RELEASE の DVD イメージをダウンロードし,ディスクユティリティで DVD-R に書き込んで,このメディアでインストールを行った。とにかく真っ先に sendmail,POP 環境を整えた。その後,上で取得したコピーデータを再度新環境に転送してからもろもろの復旧を行った。まだまだ入れ込めていないプログラムもたくさんあってうんざりである。もう二度とやりたくない。けれども,3 年に一度はやるはめになる。次はもうやる気力が起こらないかも知れない。教訓その4:自宅でサーバ運用なんてやめたほうがよい。

    最後に。警告その1:上記方法がいつもうまく行くとは限らない。 
     

    P.S.

    復旧中に,misima 旧字旧仮名変換支援サービスを使ってくれている友人から「つかえなくなっちゃったけど,どうしたの? 体調でも悪くしてサーバ運用やめちゃったの?」とのメールをもらった。ありがとう。四苦八苦しながらもリカバリしましたよ! 教訓その5:へたに自作プログラムの公開などしないほうがよい。

    下付ルビ

    |

    芭蕉俳句に関する文書を epLaTeX で作成していて,岩波文庫『芭蕉俳句集』p. 47 に出て来る「闇夜きつね下はふ玉眞桑」(妖気漂う闇の夜きつねが好物の真桑瓜に忍び寄る。男の少しおどろな夜這物語の風情がある句)をどう組むか悩んだ。この句は,「闇夜」の右側に「ヤミノヨト」,左側に「スゴク」とルビが振られていて,「やみのよとすごくきつねしたはふたままくは」と読む。「やみのよとすごく」と二度読みする(音で読んだものをさらに訓で読み下す)こういう漢文訓読法を「文選読み」というそうである。右側のルビは通常のルビ付けマクロを使えばよいが,左側に — 横書きスタイルなら下側に — ルビを付加する命令は,聞いたことがない。そんなに難しくなさそうなので,マクロを書いてみることにした。下付ルビ命令 \uruby の定義は以下のとおり。

    \newcommand\uruby[3][0zw]{\leavevmode
      % 親文字とルビの寸法を取得
      \setbox0=\hbox{#2}\setbox1=\hbox{\tiny #3}%
      % 幅の大きいほうの寸法を \dimen0 に格納
      \ifdim\wd0>\wd1 \dimen0=\wd0\else\dimen0=\wd1\fi
      % \dimen1 に「ルビ高さ+深さ+間隔値」(下にずらす量)を設定
      \dimen1=\ht1 \advance\dimen1 \dp1 \advance\dimen1 #1\relax
      % \dimen0 の幅で親文字を出力し,ルビを \dimen1 寸法だけ下に下げる
      \hbox to\dimen0{\hfil#2\hfil}%
      \kern-\dimen0\raise-\dimen1\hbox{\vbox{\hbox to\dimen0{\tiny #3}}}}%
    
    20110702-uruby.jpg

    \uruby[間隔値]{親文字}{ルビ} の書式で使用する。「間隔値」とは,親文字の下へのルビの移動量「ルビ文字の高さ+深さ」をさらに増量させたいとき,その寸法値を指定する。通常のルビ命令は奥村先生の okumacro.sty に定義されており,これと併用すれば課題は解決できることになる。例を以下に示す。

    % -*- coding: utf-8; -*-
    \documentclass[12pt]{tarticle}% 縦書き
    \usepackage{okumacro}% 奥村先生のマクロ集
    % 下ルビ命令: \uruby[間隔値]{親文字}{ルビ}
    \newcommand\uruby[3][0zw]{\leavevmode
      \setbox0=\hbox{#2}\setbox1=\hbox{\tiny #3}%
      \ifdim\wd0>\wd1 \dimen0=\wd0\else\dimen0=\wd1\fi
      \dimen1=\ht1 \advance\dimen1 \dp1 \advance\dimen1 #1\relax
      \hbox to\dimen0{\hfil#2\hfil}%
      \kern-\dimen0\raise-\dimen1\hbox{\vbox{\hbox to\dimen0{\tiny #3}}}}%
    \makeatletter
    \def\kanjistrut{\vrule \@height0.66zw \@depth0.12zw \@width\z@}
    \makeatother
    \begin{document}
    % 『芭蕉俳句集』岩波文庫,p. 47.
    \uruby[4pt]{\ruby{闇 }{ヤミノ}\ruby{夜}{ヨト}}{スゴク}きつね下はふ玉眞桑
    \end{document}
    

    奥村先生の \ruby 命令は,縦書きスタイルで使用するとルビが少し右に離れすぎてしまう。これは親文字とルビとの間に噛ませた箍 \kanjistrut を調整すればよい。また \uruby も縦書きのときは「間隔値」を指定して,もう少し左に(つまり,横書きスタイルでは下に)ずらずようにした方がよい。上記の原稿ではこの二点の調整を行っている。処理結果は右図のとおりである。

    LaTeX Mahjong fonts package PIEMF

    |

    LaTeX でも麻雀牌を組版できるということで,ptexlive on Mac OS X Snow Leopard で試してみた。Takayuki Yamaizumi 氏作成の「METAFONT 用麻雀牌ソースパッケージ piemf」である。モノトーンの麻雀牌フォントではあるが,牌構成を文書で説明する用途には十分である。副露を示す横に寝た形の牌や赤牌,暗槓の裏地も用意されている。

    METAFONT のソースとマクロファイルが添付されていて,もともと PK ビットマップフォントとして使うことが想定されている。私は mftrace でフォントを Type1 アウトライン化してインストールすることにした。さすが,j-TeX 時代に開発されたらしく,インストール説明は UNIX 向けになっている。マクロも EUC 文字コードでコーディングされているので,ptexlive UTF-8 で原稿を処理するためには,ISO-2022-JP (いわゆる JIS) に再エンコードしておくとよい。

    インストールのオペレーションを以下に掲げておく。tcsh である。

      [TEXDIR 環境変数に texmf を設定]
    % setenv TEXDIR /usr/local/texlive/texmf-local
      [作業ディレクトリ作成]
    % mkdir -p ~/tmp/mahjong
    % cd ~/tmp/mahjong
      [package ダウンロード]
    % wget -nH -nd http://www.yamaizumi.org:2481/piemf/piemf-2.0.2.tar.bz2
    % tar jxvf piemf-2.0.2.tar.bz2
      [マクロファイルを JIS に変換]
    % cd piemf-2.0.2/macros
    % mkdir -p work
    % foreach i (*.sty)
    foreach? nkf -j $i > work/$i
    foreach? mv work/$i .
    foreach? end
    % cd ..
      [マクロ,フォントソースを $TEXDIR にコピー]
    % su -m
    # mkdir -p $TEXDIR/{fonts/{source,tfm,type1,map/dvips},tex/latex}/piemf
    # cp src/*.mf $TEXDIR/fonts/source/piemf/
    # cp macros/*.{sty,fd} $TEXDIR/tex/latex/piemf/
    # mktexlsr
    # exit
      [Type1 フォントの生成]
    % mkdir -p type1
    % cd type1
    % mftrace --magnification=4000 --encoding=tex256.enc pie
    % t1binary pie.pfa pie.pfb
      [フォントマップの作成]
    % cat > pie.map
    pie pie <pie.pfb
    ^D (コントロール+D)
      [リソースを $TEXDIR にコピー]
    % su -m
    # cp pie.pfb $TEXDIR/fonts/type1/piemf/
    # cp pie.map $TEXDIR/fonts/map/dvips/piemf/
    # mktexlsr
      [マップ登録]
    # updmap-sys --enable Map=pie.map
    # exit
    

    pie2e.sty をプリアンブルで読み込んでおく。個別の麻雀牌の出力命令等の詳細は,添付の README を参照のこと。以下,試験原稿と処理結果を掲載しておく。

    % -*- coding: utf-8; -*-
    % 麻雀牌の組版
    \documentclass{jsarticle}
    \usepackage{pie2e}
    \usepackage[deluxe]{otf}
    \begin{document}
    \parindent=0pt%
    純全帯\CID{14126}九・三色同順---私の好きな役!
     
    \vspace{1zw}
    {\Huge
      \suo{1}\suo{2}\suo{3}\tong{1}\tong{2}\tong{3}\wan{1}\wan{2}\wan{3}%
      \suo{7}\suo{8}\suo{9}\tong{9}\tong{9}
    }
    \end{document}
    

    20110525-pie.jpg

    The One Ring, Tengwar fonts

    |

    J. R. R. トールキンの『指輪物語』にテングワールという謎めいた文字が出て来る。架空の言語は謎めいたファンタスティックな世界の完結性をこの上もなく表わしている。あの恐ろしい力を持つ The One Ring にモルドール語で刻印された火文字を,LaTeX tengwarscript パッケージで組んでみた。

    20110418-mordor.jpg

    One ring to rule them all, one ring to find them,
    One ring to bring them all and in the darkness bind them.
    一つの指輪は,すべてを統べ,一つの指輪は,すべてを見つけ,
    一つの指輪は,すべてを捕えて,くらやみのなかにつなぎとめる。
    J. R. R. トールキン『指輪物語1』瀬田貞二・田中明子 訳,評論社,1992年,p. 112.

    tengwarscript は CTAN: macros/latex/contrib/tengwarscript/ にある。バージョンは 1.3。フォントは何種類も出回っているが,私は AnnatarFormal だけを組込んで試してみた。上の画像は Annatar Italic である。流麗でなんとも美しい。原稿は以下のとおり。このモルドール語テクストはパッケージ添付 tengtest.tex にサンプルとして掲載されている。これを pdflatex で処理した。

    % -*- coding: utf-8; -*-
    % Tengwar on The One Ring from "The Load of the Ring" 
    \documentclass[12pt]{article}
    \usepackage[pdftex]{color}
    \usepackage[T1]{fontenc}
    \usepackage[all]{tengwarscript}
    \definecolor{charc}{cmyk}{0,0.77,0.87,0}%
    \begin{document}
    \pagestyle{empty}
    \color{charc}
    \begin{center}
    \tengwarannataritalic[2.0]
    \tengwa{254}
    \Textendedcalma\TTthreedots\Tnuumen\Tessenuquerna\TTthreedots%
    \Tungwe\Tando\Toore\TTrightcurl\Tumbar\Ttinco\TTthreedots%
    \Tlambealt\TTrightcurl\Tquesse\TTdoublerightcurl
    \Tromanperiod\Ts
    \Textendedcalma\TTthreedots\Tnuumen\Tessenuquerna\TTthreedots%
    \Tungwe\Tungwe\Tumbar\TTnasalizer\TTdot\Ttinco\TTthreedots%
    \Tlambe\TTrightcurl
    \tengwa{255}\\
    \Textendedcalma\TTthreedots\Tnuumen\Tessenuquerna\TTthreedots%
    \Tungwe\Tthuule\Troomen\Tquesse\TTthreedots\Ttinco\TTthreedots%
    \Tlambealt\TTrightcurl\Tquesse\TTdoublerightcurl
    \Tromanperiod\Ts
    \Textendedungwe\TTthreedots\Tumbar\Toore\TTrightcurl%
    \Tesse\Tkern{-0.2}\Tmalta\TTrightcurl%
    \Textendedcalma\TTdot\Ttelco\TTdot\Tquesse\Troomen\Tparma%
    \TTnasalizer\TTdot\Ttinco\TTthreedots\Tlambe\TTrightcurl
    \end{center}
    \end{document}
    

    tengwarscript のインストールは通常の LaTeX パッケージ組込となにも変わらない。ダウンロードした tengwarscript リソースを TDS に準拠して TeX ツリーに格納し,tengwarscript.mapupdmap-sys で登録する。フォントもほぼ同じだけれど,Formal については TrueType フォントファイル名を tengwarscript.map の記載に合わせてリネームする必要がある(もちろん map を訂正してもよい)。テングワール文字の出力命令については,ドキュメント tengwarscript.pdf を参照。
     

    ※ 4.20 付記

    一応,端末上のインストール・オペレーションもメモっておく。端末操作は tcsh シェルである。TeX ツリーはローカル texmf とし,そのトップ・ディレクトリ名称を /usr/local/texlive/texmf-local としている。

    まず,作業ディレクトリ(ここでは ~/tmp/tengwar)で tengwarscript パッケージをダウンロード,展開し,dtx, ins ファイルを latex 処理する。dtx はドキュメントでもあり,以下の操作ではリファレンス解決のため 2 回実行している。もとより PDF はすでに添付されているのできちんとした dvi ファイルが不要ならば,1 回の処理でもよい。

    % mkdir -p ~/tmp/tengwar
    % cd ~/tmp/tengwar
    % set WGET="wget -nH -nd"
    % set TEXDIR="/usr/local/texlive/texmf-local"
    % $WGET http://mirror.ctan.org/macros/latex/contrib/tengwarscript.zip
    % unzip tengwarscript.zip
    % cd tengwartscript
    % latex tengwarscript.dtx
    % latex tengwarscript.dtx
    % latex tengwarscript.ins
    

    次に,Annatar 及び Formal フォントをダウンロード,解凍する。Formal フォントについては,map の記述に合うようにファイル名を変更する。

    % $WGET http://home.student.uu.se/j/jowi4905/fonts/tngan120.zip
    % unzip tngan120.zip
    % $WGET http://tengwarformal.limes.com.pl/fonts/TengwarFormal-12c-ttf-pc.zip
    % unzip TengwarFormal-12c-ttf-pc.zip
    % cd TengwarFormal-12c-ttf-pc/fonts/
    % mv TengwarFormal12b.ttf TengwarFormal12.ttf
    % mv TengwarFormalA12b.ttf TengwarFormalA12.ttf
    

    su -m で特権ユーザとなり,上で展開したリソースを TeX ツリーに格納し,フォントマップ登録を行う。

    % cd ~/tmp/tengwar/tengwarscript
    % su -m
    # mkdir -p $TEXDIR/{doc/latex,tex/latex,fonts/{tfm,vf,type1,truetype,map/dvips,enc/dvips}}/
    tengwarscript
    # cp -p *.pdf $TEXDIR/doc/latex/tengwarscript/
    # cp -p *.cfg *.sty $TEXDIR/tex/latex/tengwarscript/
    # cp -p tfm/* #$TEXDIR/fonts/tfm/tengwarscript/
    # cp -p vf/* $TEXDIR/fonts/vf/tengwarscript/
    # cp -p map/* $TEXDIR/fonts/map/dvips/tengwarscript/
    # cp -p enc/* $TEXDIR/fonts/enc/dvips/tengwarscript/
    # cp -p *.ttf $TEXDIR/fonts/truetype/tengwarscript/
    # cp -p TengwarFormal-12c-ttf-pc/fonts/*.ttf $TEXDIR/fonts/truetype/tengwarscript/
    # mktexlsr
    # updmap-sys --enable Map=tengwarscript.map
    

    以上。

    新常用漢字表

    |

    「𠮟」という文字がいまあなたのモニタに表示されているだろうか? 私の愛用する Mac OS X Snow Leopard: Safari 5.0.4 あるいは Mac OS X Tiger: Safari 4.1.3 ならしっかり見えるが,Windows ユーザはどうだろうか? Windows 7 なら大丈夫か。XP ユーザは見えないのではないだろうか? なんでこんなことを記すのかというと,昨年,11 月 30 日に告示された改訂常用漢字表にこの文字が追加されたからである。1981 年以来の改訂である。なんとその前は 1946 年。

    上の「𠮟」という文字は「しかる」という訓を持つ字である。口偏に「七」と書く「しかる」。「しかる」と入力して仮名漢字変換されて普通に出て来る「叱」なら,おそらく誰のモニタにも表示されるはずである。ところが,いつもパソコンで入力している「叱」という文字のほうは誤字であって,「𠮟」こそがじつは正しい「しかる」なのだ(『字源』には「俗に叱と作るは非」とある)。JIS がはじめのボタンを着け間違えた次第なのである。ここに来て常用漢字表に正しい字体が入れられた。

    文字「𠮟」は Unicode CJK Unified Ideograph U+20B9F というコードポイントを持つ。いわゆるサロゲートペアと呼ばれる拡張領域(Extension B)に配置されている(JIS では JIS X 0213:2000 第三水準区点 4752)。この領域をきちんと読書きできるソフトウェアはまだ多くないのではなかろうか。私はここで「𠮟」の文字を Mac OS の「ことえり」で入力したわけではない。多言語テキストエディタ GNU Emacs 24 の ucs-insert 関数に 20b9f を指示して得られたものを,コピペしたんである。

    そう,そんな特殊なところにある文字(正しい文字なんだけど)が常用漢字表に収録されたんである。このことはいまあんまり騒がれていないが,官庁,地方自治体はもとより,出版や報道業界にも少なからざる影響が出るはずである。その筋のシステム屋は,もうすでに,これら常用漢字表にあるにも拘らずシステムで表示・印字できない文字の取扱いに悩まされているはずである。大騒ぎにならないうちになんとかしなくっちゃと。

    官庁・地方自治体のシステムは,つい最近に設計された新しいものは別として,Unicode 対応すら進んでいないのが実情だと思う。文字コードは JIS X 0208:1983, :1990 準拠が基本ではなかろうか。漢字コード草創期の設計を踏襲して大きくなって来た官庁・自治体システムは,人名許容漢字の法令に従わなければならず,貧弱な JIS X 0208 を補うために,JIS 第一水準/第二水準にない文字のために大量の外字を抱えている。そして,文字周りはいろんなところに影響するので,おいそれとは改修できない。(同じようなことを JIS 2004 についても書いたような気がする。)

    JIS の独善的標準なら「システム仕様として対応してません」で済むかも知れないが,こと日本国政府ご指定の「常用漢字」に扱えない文字があるとなると深刻である。国民との文書交換を考慮すれば,世の官庁・自治体もこの常用漢字改訂で Unicode 対応システム改修に重い腰を上げることになると思う。また税金が本質的でないことのために消えて行くというわけである。漢字・仮名遣いという悪魔的正書法に立つ日本は,いつまでたっても文字を巡る混乱から解き放たれない宿命にある。「歴史的仮名遣いに戻せ」云々のエセ文化人による戯言よりも,もっと高度で抜差しならない宿命である。
     

    * * *

    ことのついでに,「𠮟」のようなサロゲートペア文字をどうしても入力できないとき,Perl で出力させるワンライナーを示しておく。Perl 5.8.5 くらいのバージョンが必要だと思う。

    端末コマンドラインから次を叩くと,「𠮟」(=U+20B9F)が出力される。(※ 4.27: 少し簡略化した)

    % perl -e 'binmode STDOUT, ":utf8"; print pack("U", 0x20b9f);'
    

    あるいは,

    % perl -e 'binmode STDOUT, ":utf8"; print "\x{20b9f}";'
    

    20b9f のところに必要な文字の Unicode コードポイント十六進数を指定する。20bb7 を指定すると「𠮷」(吉野家のつちよし)が出力される。この出力をファイルにでもリダイレクトしてコピペして利用すればよい。Mac OS X Tiger 以降のターミナルなら端末上にきちんと文字が表示されるので,それをコピーすればよい。もちろんターミナルの LC_ALL 環境変数は ja_JP.UTF-8 に設定されていなければならない(これはデフォルトのはず)。

    Unicode コードポイント十六進数は,学研の『漢字源』なら JIS コードとともに掲載されている。何の役にもたたない『JIS 漢字字典』なんて高価本を買うのはやめて,ぜひこちらを座右に置いていただきたい。私は改訂第四版を愛用しているが,最近第五版が刊行された。

    Windows Vista, Windows 7 なら IME パッドから CJK Unified Ideograph Extension B 領域の文字を入力できると思う。Office も 2007 ならきちんと使えるはずである。
     

     

    ※ 4.26 付記

    この記事を書いたあと,ネットを渉猟していたら,京大の安岡孝一先生(文字コードに関する権威でもある。日本語文字コードに関する見識において私が尊敬する学者のひとりである)が,2009 年 12 月の段階で「新常用漢字表が迫るUnicode移行,『シフトJIS』では対応不可能」というコラムを ITpro に掲載し,新常用漢字表の改訂について IT 業界に警鐘を鳴らしていた。さすがである。

    上で「いつもパソコンで入力している「叱」という文字のほうは誤字であって」と書いた。この記述そのものを訂正するつもりはないのだけれど,「叱」という字体はすでに一般に浸透しており,もはや「誤り」とするのは時代錯誤というものである。出版の世界でも「しかる」「しっせき」に対して「叱」を表記しても,「無知無教養」でも「恥ずかしい」わけでもない。きちんとした国語辞典,しかもコンピュータの利用が一般化する以前に出版された新潮国語辞典(昭和 40 年初版)でもすでに「叱る,呵る」となっている(ただし,私の手元にある昭和 58 年刊岩波書店『広辞苑』第三版では「𠮟」という正字体のみが掲載されているし,平成 14 年再版・大修館書店『新漢語新辞典』では正字体「𠮟」のみが見出しになっており「叱」字体については「叱(口を開くさま)は別字であるが,俗に混用する」という記事がある)。つまり,JIS 第一・第二水準における「叱」採用は,JIS が「間違った」というよりも,もとよりこうした俗化・混用の「定着」の反映と捉えるべきである。
     

    ※ 2011.5.28 付記

    会社の Windows XP professional Version 2002, SP3 + IE 6.0.2900.5512 では「𠮟」がきちんと表示された。どのタイミングかのパッチで改善されたようである。

    Moon Calendar

    Profile

    ISAO YASUDA。システムエンジニア。神奈川県在住。昭和 30 年代を懐かしむオヤジ。ロシアに興味があります。
    [more], [About our site]

    Notice

    この文書はフィクションであり,実在する個人,団体等とは一切関係ありません。

    R-18 指定サイトです。そのうち「18 歳以上ですか」の認証を入れる予定です。

    文書の記述内容は無保証です。不適切な表現があればコメントにてご指摘ください。

    コメント,トラックバックは,現在,運用を停止しています。ご意見等ありましたら isao@yasuda.homeip.net 宛電子メールにてお願いします。

    Links

    About this archive

    All Entries of Category MacOSX

    Previous: Linux

    Next: TeX

    Recent Entries in Main Index.
    All Entries in Archive Index.

    February 2012

    Sun Mon Tue Wed Thu Fri Sat
          1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29      
    Powered by Movable Type 4.1 blog counter