MH のどうでもいいような趣味の講座


「MH 中級者以上向け〜どうでもいい(笑)ような趣味の講座」
 ◎第2章: MH フォーマット入門
MH フォーマットは、いくつかの MH コマンドプログラム中での フォーマット指定ファイルで使用されるもので、 メールを作成したり表示したりするという「出力」の際に、 メールのヘッダや中身などの情報をマクロに取り込んで制御するものである。

慣れてしまえば簡単…とはいうものの、 あまりにもカスタマイズが可能な様になっていることと、 パッと見ただけでは判り難いその制御コマンドのややこしさとに圧倒され、 ちょっと触りづらいものになってしまっている。

さらに、当初 MH フォーマットに関して詳しく説明した本が……なかなかない(苦笑)。 MH 自体の本や、MH コマンドプログラムの使用方法について 書かれた本や雑誌記事はかなり多くなったものの、残念ながら MH フォーマットに関するものは P.D.Stallmen による紹介記事 [4] 位しか 見当たらなかった。 尚、MH-6.8 からはオンラインマニュアルも改善されたので、 これをまず参照することをお勧めする。

ここでは、簡単なファイルの実例を中心にいくつか紹介・解説する。


● scan.timely の設定解説

フォーマットファイルは、エスケープシーケンスの集まりである。 MH フォーマットの基本として覚えておくことは、以下の点である。

・エスケープシーケンスは 「%」 で始まる
つまり各コマンドの区切りである
・2つのレジスタが存在する
文字用のレジスタ (str) と数値用 (num) がある
%{ }
メールヘッダ内容。例えば「%{to}」は To: ヘッダの内容を表す (大文字小文字の区別はない)。 行なった結果は文字用レジスタ str にセットされる
%( )
組込関数の呼び出し。 例えば、「msg」関数を使用するなら、「%(msg)」とする。 この結果は、数値を返すなら num レジスタ、文字列を返すなら str レジスタ にセットされる
関数が入れ子になっている場合や特定の組込関数 (voidなど) の場合以外は その内容が画面に出力される
組込関数の呼び出しについては、その引数として
[表2-1] 組込関数とその書式例
引数 解説 書式例
---- 引数を取らない %(func)
literal 数値や文字列を直接与える %(func 1234)
%(func strings)
comp
date
addr
メールヘッダの内容 %(func {subject})
%(func {date})
%(func {from})
expr 関数の入れ子や判別文などの 結果を引数として与える %(func (func2))
%(func (func2{comp}))
%(func %<{reply-to}%|%{from}%>)

等の種々のものが可能である。書式例にある様に、引数となる (入れ子となる) ものには「%」をつける必要はない。

・コントロールシーケンス %<, %|, %>
%< =if、%| =else、%> =endif である
mh6.7.2 以降は、elsifとして「%?」が使用出来る
・行末の「\
改行のエスケープ。行が次行に継続することを示す。例えば
%{To}\
%{From}
では、Toヘッダと Fromヘッダの内容が続けて1行に表示されるが、
%{To}
%{From}
では、Toヘッダで1行、改行して Fromヘッダの内容が表示される
・画面出力時の書式指定
上述した様に「%{ }」「%( )」で画面にその内容が出力されるが、 「%4{ }」「%4( )」等とすると、サイズ指定で出力できる
C言語等と同様に、「%04( )」で「0016」などのゼロサプレスしない表記や 「%-5{ }」で右詰め表記なども可能である。
レジスタの桁数よりも表示桁数が少ない場合、
・コメント行
「%;」から (\をつけない)改行まで をコメント行とみなされる
ではまず簡単な例であるが、
%<(mymbox{from}) To: %{to}%>
この様に入れ子の場合、内側のエスケープシーケンスでは「%」をつける必要がない。
この MH フォーマットは、
  1. {from} で、From: ヘッダの内容を str レジスタに入れる
  2. mymbox 関数が str レジスタを読み、結果を num レジスタに返す
  3. 制御エスケープシーケンスが num レジスタを評価する
    真なら 「 To: 」+ To: ヘッダの内容 を出力する
という手順で処理される。

では、MH 附属の MH フォーマットファイルの解説に移ろう。
MH の Lib path (例: /usr/local/mh/lib) にある scan.timely (MH-6.7.2) は、

scan.timely
[リスト2-1] 附属 scan.timely ファイル (MH-6.7.2)
となっている。これをコマンドラインから用いるのには、自分の MH の Mail path へ コピーして、

% scan +メールフォルダー名 -form scan.timely

とすればよい。この``scan.timely''フォーマットファイルを使用 すると、
scan.timely使用例
[図2a] scan.timely使用例
の様に、メッセージ番号・時刻・差出人・Subject・内容の一部 が 1行に順に表示される。

このscan.timelyでは、 以下の2点について 条件判別で表示を変えるようになっている。

  1. 「時刻表示」は、 を表示する
  2. 差出人が「自分」の場合は、「どこに出したのか」を表示する
まず1行目であるが、
%4(msg)<%<(cur)+%| %>%<{replied}-%|%<{encrypted}E%| %>%>\

%4(msg)
メッセージ関数 msg (戻り値: メッセージ番号を整数で)を 4桁で表示
%<(cur)+%| %>
カレント関数 cur の戻り値により判別を行なう。
条件判別説明
[図2b] 条件判別の説明
という意味で、そのメールがカレントであれば「+」を表示し、 なければ1文字空白を表示する。 カレント位置の情報は、そのメールフォルダの .mh_sequence ファイルを参照する
%<{replied}-%|%<{encrypted}E%| %>%>
これも同様に、ヘッダに「replied」がついていれば、「-」を 表示し、ない場合、さらにヘッダに「encrypted」があれば「E」 を表示し、どちらもなければ1文字空白を表示する
行末の \
改行のエスケープ。次行への連続を意味する
さあここから上述した「条件判別」の1つ目である。 そのメールの Date: ヘッダの値により、色々と判別を行なう。
%(void(rclock{date}))\
「(rclock{date})」は、{date}で Date: ヘッダの内容を取り出し、 組込関数 rclock に与えている。rclock は「現時間との差」を秒で返す 関数である。その値を、num レジスタに void 関数でセットしている
%<(gt 15768000)%03(month{date})%02(year{date})%|\
あとは、この numレジスタの値との比較を行ない、条件判別している。 if その値が、15768000 (秒。=約半年) より大きければ、 月を3文字、年を2文字で表示
%<(gt 604800)%02(mday{date})%03(month{date})%|\
else if 604800 (1週間)より大きければ、日を2文字、月を3文字で表示
%<(gt 86400) %(day{date}) %|\
else if 86400 (1日)より大きければ、曜日で表示
%02(hour{date}):%02(min{date})%>%>%>\
else 時を2文字 + 「:」 + 分を2文字 表示。endif endif endif
%<{date} %|*%>\
ヘッダに Date: フィールドがあったら1文字空白、 なければ「*」を表示
これで「時刻の条件判別表示」は終了。次に「差出人の条件判別表示」である。
%<(mymbox{from})To:%14(friendly{to})%|%17(friendly{from})%> \
(mymbox)は、From: ヘッダを参照して「自分が出したメールかどうか?」を 判別する関数である。「自分である」アドレスへの登録追加は、前述した様に .mh_profile での Alternate-Mailboxes で指定出来る。 つまりここでは
「mymbox(from)が真なら、To:%14(friendly{to}) を、
偽であれば、%17(friendly{from})を実行する」
のである。尚 friendly 関数は、そのアドレスを見やすい形に変換する ものである。 自分が出したものであれば、「To:」+ 14文字でヘッダの To: フィールドを、 自分が出したものでなければ、17文字でヘッダの From: フィールドを表示するの である。
%{subject}%<{body}<<%{body}%>
あとは、%{subject} で、ヘッダの Subject: フィールドが表示され、 更にメール中身を示す body 関数を用い、メールの中身があれば それを「<<」のあとに表示する設定になっている。 勿論メールの中身はなかなか10数文字で終るものではないのだが、いくら その文字列が長くとも、scan コマンドが、表示出来る横幅に合わせて 勝手に切ってくれる
ちなみに上記の関数の説明は、MH-6.7 レベルでの話で、MH-6.8 では 基本的にはほとんど一緒だが多少変更されている。MH-6.8 附属の scan.timely は、
scan.timely (MH-6.8) [リスト2-2] scan.timely ファイル (MHー6.8)
となっている。変更部を説明すると
%(void(year{date}))%02(modulo 100)\
mh-6.7 迄のように甘くなく:-)レジスタの扱いが厳しくなった為、 %02(year{date}) という形で数値 (year関数はinteger) を 文字列的に直接操作することが出来なくなったので、 一旦 void で num レジスタに退避させ、次の modulo 関数で 100 で 割った余り(つまり西暦の下2桁)を str レジスタに放り込み、 %02 で出力している
%?(gt 604800)…
このあたりは、%? (=elsif) が使えるようになったことによる
%<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)…
ここは若干判別を増やし丁寧にしたのと、 mymbox 関数が整数 (真=1, 偽=0) を num レジスタに返すので、 num レジスタが 0 であれば真 という zero 関数 を用いて判別するように変更している
以上が scan.timely の説明である。 これらの MH フォーマットの設定により、図2a の様な表示が可能なのである。
● inc/scan用 MH フォーマットファイルの設定変更例解説

さて実際に設定を変更する場合の手順としてはどうすればよいのか?

  1. まず MH の Lib path にある scan.timely などのサンプルファイルを 自分の MH path の下に名前をつけてコピーする
  2. そのファイルをいじりながら、 「scan +inbox -form そのコピーしたファイル名 last:-20」 など として、コマンドライン等から実行してみて、思ったように表示が出来るかを 繰り返し試しチェックする
  3. 完成すれば、前述した様に ~/.mh_profile で、その MH フォーマットを 使用する MH コマンドに対し、-form オプションで指定する
という手順になるであろう。

では今回、scan.timely ファイルを基にして、 以下の様に設定したい…とする。

1. inc.form

まず scan.timely を 自分の MH path の下に inc.form という名前でコピーした とする。

inc.form 使用例
[図2c] inc.form 使用例
この様な表示が出来ることを目標とする。 図2a と見比べれば判るが、基本的な骨格は上記の scan.timely ファイルそのままなので、 変更点のみ説明する。

メッセージ番号の表示部分・日付/時刻の表示部分に関してはそのままである。 その次の「(^^;) 」や「[98] 」などの6文字分が追加挿入表示設定した 部分である。
今回、「inc 時には差出人によって少し表示を変更」を行ない、

するように考えてみる。

MH フォーマットでは結局、メールのヘッダ部分の情報を用いて 制御するのが主であるので、条件判別の Key を探すにはまず、 これら3つのメールについての「 ヘッダの違い」を考える。 「差出人によって…」ということであるから、当然「From: ヘッダ」が 異なる筈であるのでこれに着目する。このヘッダ情報を文字列によって 判別する、ということを行なうには、

  1. void 関数 …ヘッダ情報を取り出して str レジスタに入れる
  2. match 関数 …str レジスタの文字列と比較を行なう
の 2 手順を考えればよい。従って
MH フォーマット例
[リスト2-3] MH フォーマット例 (MH-6.7.1 以前)
MH フォーマット例
[リスト2-4] MH フォーマット例 (MH-6.7.2 以降)
などとし、
  1. From: ヘッダの中身を void 関数で str レジスタに入れる
  2. str レジスタに引数で与えられる文字列が含まれるかどうかを調べる match 関数で ``98game'', ``hayashi'' という文字列が含まれているか判別する
という手順で、それぞれ「[98] 」、「(^^;)」を 表示している。この様な手順を繰り返すことで様々な判別が可能である。

但し今回のこの設定では、実際には不充分となることも多い。

  1. Mailing List などからのメールでは、その From: ヘッダが、 ML名でなく、そのメールを ML に投稿した人になっている場合がある
  2. hayashi という文字列に match するだけなら、別の hayashi さんからの メールでも自分の出したメールだと誤判断し「(^^;)」を表示してしまう
そこで、 等の厳密な判別への工夫が必要となる。

2. scan.form

これも同様に、変更点のみ説明する。

のであるから、その部分がちょっと違うだけである。

その ML のシステムによっても異なるが、ML からのメールには、 メールヘッダ部に「その ML での通しメール番号」が付けられていることが多い。 例えば「X-Count: 00123」とか「X-Ml-Counter: 00234」等といった行である。
従って、自分の加入している ML が どういうヘッダにどういう形式 で通しメール番号をつけているのかを把握し、それに対応して MH フォーマット ファイルを記述すればよい。
例えば、加入している ML で「X-Ml-Count: 00080345」というヘッダをつけている とする。このヘッダの中身を数値として取り出すには、compval 関数を用いると よい。

%04(compval{X-Ml-Count})
このままでは、「X-Ml-Count:」ヘッダのない、他からのメールに対しても この MH フォーマットを実行しようとしてしまうので、コントロールシーケンス により判別を行ない、
%<{X-Ml-Count} %4(compval{X-Ml-Count})%>
とするとよい。
これで「X-Ml-Count:」ヘッダが存在すれば、その中身を num レジスタに取り込み、 かつ入れ子になっていないので表示(4桁で出力)されることになる。

この判別・出力部分を scan.timely の希望の場所に書き加えることで、

scan.form 使用例
[図2d] scan.form 使用例
などという具合に表示される。

実際にはこの「通しメール番号」を与えるヘッダが ML によってもまちまちで あったりするので、うまくコントロールシーケンスを用いて判別し、 そのメール番号情報を取り出す様に、MH フォーマットを書くことになる。


● MH-6.8-JP2 での組込関数の拡張について

MH-6.8.X-jp2x では、MH-6.7.X レベルでの日本語化に加え、 RFC-1521,1522 対応の mime-encode/decode が可能になるよう拡張されている。 (参照: mh-6.8-JP-README.2 の「[3.6.2] mh-format による方法」)

これにより、MH フォーマットの組込関数に、

[表2-x] MH-6.8-JP2 での拡張組込関数
関数名引数戻り値内容
hencodeexpr文字列日本語文字列を RFC-1522 形式に変換
hdecodeexpr文字列RFC-1522 形式を 日本語文字列に変換

の2つの文字列操作関数が拡張されている。

これらの関数を MH フォーマット中の RFC-1521形式 の charset="ISO-2022-JP" 文字列が出てくるところ、 例えば 「%17(friendly{from})」を「%17(hdecode(friendly{from}))」とか、 噛ませてやるだけで OK です。

この MH-6.8.X-jp2x での hencode/hdecode 関数の拡張は、 MH config 時の「option MIME」とは無関係であり、 jp2x パッチを当てた時点でこれらの関数が使えるようになることに注意。


● 参考) MH-6.8 とその日本語化の歴史(笑)

1992年12月下旬
1992年12月15日に MH-6.8 がリリースされた直後、 高田@NTTさんと私が偶然ほぼ同時 に、全く別系統で 作った日本語化パッチを fj に投稿。 すぐに統一され MH-6.8-JP-patch.1 となる。 これは従来の MH-6.7.2 での日本語化パッチ (てつ@京大・情報さん、児島@電総研さんらによる。 漢字(2バイト文字)の表示を可能にしたもの) を 6.8 に拡張apply しただけのもの。
1993年2月14日
高田@NTTさんを中心として mh-6.8-JP-patch.2 を発表。 これは上述した RFC-1341,1342 (現在は obsolate。RFC-1521,1522 に) 形式の文字列を扱う拡張等を含めたもの。 2a (同月)、2b (同年3月)、2c (同年8月) とバグフィックスが続く
1993年夏〜
MH 本体が MH.6.8.1 (同年8月)、MH.6.8.2 (同月)、MH.6.8.3 (同年11月) と、 version Up される。 JP2x を 6.8.3 に当てるにはほんのちょっとの些細な変更が必要であったので、 誰かがまとめて下さったのでしょうか、気づくとに日本各地の anonymous-ftp に、 mh-6.8.3-jp2c.tar.gz という形で、すでにパッチを当てたのが置かれるように
1995年8月
私がそれまで使用していた HP-UX での改良点や、 新天地の Solaris2.x WS での移植経験をまとめたものや、 細かい改良・Bug Fix を 渡邉@神戸大さんとまとめて、 unofficial patch mh-6.8.3+-jp2c+.patch (v1.00) として、fj.sources 他に投稿
1996年2月
MH.6.8.4 がリリースされる。 但し、現在まで飽く迄もβテストリリースであり、「正式」リリースではない模様
1996年8月
渡邉@神戸大さんとの mh-plus プロジェクトにより、 unofficial patch mh-6.8.3+-jp2c+.patch (v1.02) をリリース

© Haruhisa Hayashi 1992,1997
気まぐれにより個々のファイル名を変えてしまう可能性もありますので、 Link/Bookmark される場合は、 %7Ehayashi/internet/mh_guide.html にお願いします。
目次へ 第0章へ 第1章へ 第2章へ 第3章へ
直接このファイルに飛んで来た場合… フレーム版メニューへ
by はやし はるひさ hayashi あっとまーく laic.u-hyogo.学術.日本