UNIX/Linuxの部屋 コマンド:m4

TOP UNIX/Linuxの部屋 UNIX/Linuxコマンド一覧 用語集 新版 由来/読み方辞書 環境変数マニュアル Cシェル変数 システム設定ファイル システムコール・ライブラリ ネットワークプログラミングの基礎知識 クラウドサービス徹底比較・徹底解説




コマンド m4 マクロ言語プロセッサ このエントリーをはてなブックマークに追加

m4 は cpp のように文字列の置き換え・ファイル取り込みなどを行うマクロ言語プロセッサである。m4 は cpp より強力だが、その分わかりづらい。m4 を詳細に説明すると本が一冊書けてしまうので、概要だけ述べる。



m4 コマンドの基本的な使い方
まずはコマンドラインから m4 を実行してみよう。m4 が何の役に立つかわからない人はつまらなく思うかもしれないが、最初に基本的な機能だけは抑えておくと理解しやすい。

入力行は便宜的に「>」を付けたが、実際にはこのようなプロンプトは表示されない。
% m4
> hoge
hoge
⇒ hoge を入力したら hoge が出力された。m4 の最も基本的な機能は、入力をそのまま出力することである。
> define(hoge, `fuga')
⇒ マクロ hoge を定義。入力に hoge を発見したらfuga に置換するということ。fuga を囲むクォートは '〜' ではなく `〜' (バッククォートとシングルクォート) であることに注意。
> hoge
fuga
⇒ マクロ hoge を定義したため、fuga に置換された
> hogehoge
hogehoge
⇒ hogehoge は fugafuga とはならない。m4 は単純に文字列を置換するわけではない。なぜなら、hogehoge は hoge と hoge が連続したものとは見なされず、hogehoge というひとつの入力 (正確にはトークン) と見なされる。
> hoge hoge
fuga fuga
⇒ 空白で区切れば別のトークンとなるので、hoge hoge は fuga fuga に置換される。
> define(abc, `hoge')
⇒ マクロ abc を定義。abc は hoge に置換される。
> abc
fuga
⇒ abc は hoge ではなく fuga となっていることに注意。
define(hoge, `fuga')
define(abc, `hoge')
という 2つのマクロ定義により、abc → hoge → fuga となる。つまりマクロ展開後の文字列がマクロと見なせる場合、再度マクロ展開が行われる。これ以降はマクロと展開後の文字列を混同しないよう、マクロ名は「アンダーバー + 大文字」に統一する。
> define(_ARGS, `args are $1 $2')
⇒ マクロには引数を渡すことができる。マクロ定義部では $1・$2… で 1番目・2番目… の引数を扱うことができる。
> _ARGS(`aaa')
args are aaa
> _ARGS(`aaa', `bbb')
args are aaa bbb
> _ARGS(`aaa', `bbb', `ccc')
args are aaa bbb
⇒ マクロ _ARGS では 2つの引数しか使用していないため、3つ目以降の引数は無視される。
> define(_ARGS, `args are $* [$#]')
⇒ 全ての引数を表すには $* を、引数の数は $# を使う。ちなみにこれは上で定義したマクロ _ARGS を再定義していることになる。
> _ARGS(`aaa', `bbb', `ccc', `ddd')
args are aaa bbb ccc ddd [4]
⇒ 全ての引数と、引数の個数に展開されている。

m4 にはいくつかの組み込みマクロがあらかじめ用意されている (define もその一つ)。
> include(`hoge.txt')
⇒ ファイル hoge.txt を取り込む。もしファイルが存在しなければエラー。ファイル内容もマクロとして評価されることに注意。
> syscmd(`コマンド')
⇒ コマンドを実行した結果を取り込む。

コマンドラインからマクロ定義をいちいちタイプしたくないだろうから、ファイル (この例では input.txt) に
define(_HOGE, `fuga $1')
_HOGE(`abc')
と入力内容とマクロ定義をあらかじめ記述しておき、
% m4 input.txt > output.txt
としてマクロ展開を行うのが一般的な使い方である。

もし入力内容とマクロ定義を分離しておきたければ、適当なファイル、例えば input.m4 に
define(_HOGE, `fuga $1')
とマクロ定義を記述し、入力ファイル内で
include(`input.m4')
_HOGE(`abc')
とマクロ定義ファイルを include すればよい。こうすることで、一つだけマクロ定義ファイルを作成しておいて、複数の入力ファイルで使いまわすことができる。

サンプル
より実践的な例として、HTML を m4 で生成してみよう。当ページ管理人は HTML ファイルを手で書いているわけではない。エディタで
のようなプレインテキストを作成し、これを自作の Perl スクリプトに喰わせることで
という HTML ファイルを自動生成している。

自作スクリプトの主な機能は以下の通り。
  • 「:start ほげほげ」と書けば HTML・HEAD・TITLE・BODY 要素が出力され、「ほげほげ」がページタイトルとなる
  • 「:term ふがふが」と書けば項目の見出しとなる
  • 「:cmd」〜「:cmd」で囲むとコマンドの実行例として表示される
  • 「:code」〜「:code」で囲むとソースの例として表示される
  • 「:preincude ファイル名」と書くとソースファイル全体が取り込まれる
  • このとき、ソースに行番号を付加する
  • さらにソース中の <・> を &lt;・&gt; に置換する (実体参照化)
  • 「:preincude ファイル名 42-45」と書くとソースファイル中の指定した行番号部分が取り込まれる
  • 「:preincude 42-45」とファイル名を省略すると、前回指定したファイル名が取り込まれる
  • ファイルの終わりには自動的に cvs の $id: ヘッダや BODY・HTML 要素の閉じタグが出力される

上記の細かな仕様はどうでもよい。これと同じことを m4 を使って実現してみよう、というのが目的である。

結果として、以下の機能変更を行った。
  • m4 ではコロンを含むマクロ名は使えないので、アンダーバーから始まるマクロ名に変更した。
  • 「:cmd 〜 :cmd で囲む」といった状態保持が必要な書き方は m4 的ではないので、「_CMD_BEGIN 〜 _CMD_END で囲む」に変更した (define で現在の状態を保存すれば、トグル的な動作も可能だろう)。
そしていろいろ試行錯誤してみたが、FreeBSD 5.2.1-RELEASE の m4 では、以下の機能は実現できなかった。
  • 「:preincude ファイル名 42-45」と書くとソースファイル中の指定した行番号部分が取り込まれる
  • 「:preincude ファイル名」で取り込んだソース中の <・> を &lt;・&gt; に置換する
最終的に作成した m4 マクロは以下の通り。
define(_HTML_BEGIN, `<HTML><HEAD><TITLE>$*</TITLE></HEAD><BODY><H1>$*</H1>')
define(_HTML_END, `<P CLASS="cvsid"><A HREF="_CVSWEB_URL">_CVSID</A></P></BODY></HTML>')
define(_TERM, `<H2>$*</H2>')
define(_CMD_BEGIN, `<PRE class="cmd">')
define(_CMD_END, `</PRE>')
define(_CODE_BEGIN, `<PRE class="code">')
define(_CODE_END, `</PRE>')
define(_PREINCLUDE, `<PRE class="code">syscmd(cat -n $1)</PRE>')
define(_CVSID, `$Id: prgmemo.src,v 1.86 2017/07/22 11:11:00 68user Exp $')
define(_CVSWEB_URL, `../cgi-bin/cvsweb.cgi/public_html/net/org/'__file__)
m4wrap(`_HTML_END')

_HTML_BEGIN(`RSA で暗号化してみよう (2)')
_TERM(`自分で暗号化・復号化')

前ページで作成した (略) 実行結果は以下の通り。

_CMD_BEGIN
% cc -o rsa-2 rsa-2.c -lcrypto
(略)
_CMD_END

まずは RSA の鍵生成です。前ページの rsa-1.c では

_CODE_BEGIN
RSA *rsa = RSA_generate_key(KEYBIT_LEN, 65537, NULL, NULL);
_CODE_END

だけで済んでいましたが、今度は自分で計算しなくてはいけません。

_PREINCLUDE(`rsa-2.c')
(略)

以下、落ち穂拾い。

コメント
m4 のコメントは # から行末までである。ただしマクロ解析の対象とならないだけで、コメントもそのまま出力されてしまうことに注意。
% m4
> #hogehoge
#hogehoge
出力したくないコメントは
divert(-1)
# コメント
# コメント
divert(0)dnl
とすればよい。意味については組み込みマクロ divert の項を参照。
dnl
dnl は「Delete through NewLine」の略で「ここから行末まで破棄」という意味である。例えば define でマクロを定義すると、余分な空行が出力されることに気がついただろうか。
% m4
> define(foo, bar)
← この空行は何?
> foo
bar
m4 は入力された文字をそのまま出力しようとするため、define(foo, bar) の後に入力した改行コードがそのまま出力されたのである。これを防ぐには
% m4
> define(foo,bar)foo
bar
と続けて書けばよいが、これでは見づらくて仕方がない。そこで、
% m4
> define(foo, bar)dnl
> foo
bar
と define の後に dnl を付けて、「ここから行末まで破棄しなさい」と m4 に指示することで余計な改行コードが付かないようにすることができる。

組み込みマクロ一覧
divert・undivert
m4 には 0〜9 までの出力キューが存在する。デフォルトは 0 で、
% m4
> hoge
hoge
は出力キュー 0 に対して「hoge」を出力し、即座に画面上に表示されたということである。divert マクロによって出力する出力キューを選択できる。全ての処理が終了した後、1〜9 の出力キューの中にたまっているキューの内容が、出力キュー 1→9 の順で出力される。
% m4
> divert(2)
> this is queue 2
⇒ 出力キュー 2 にためている
> divert(1)
> this is queue 1
⇒ 出力キュー 1 にためている
> divert(0)
> this is queue 0
this is queue 0
⇒ 出力キュー 0 はすぐ出力される
^D (Ctrl-d で標準入力をクローズ)
this is queue 1
this is queue 2
⇒ 出力キュー 1〜9 の内容が出力される
任意の時点で出力キューの内容を取り出したい場合は、undivert すればよい。
% m4
> divert(2)
> this is queue 2
⇒ 出力キュー 2 にためている
> divert(0)
⇒ 出力キューを 0 に切り替え
> undivert(2)
⇒ 出力キュー 2 の内容を現在の出力キュー (キュー 0) に移動。結果として画面に出力される
this is queue 2

> divert(3)
this is queue 3
⇒ 出力キュー 3 にためている
> divert(1)
⇒ 出力キューを 1 に切り替え
> undivert(3)
⇒ 出力キュー 3 の内容を現在の出力キュー (キュー 1) に移動。
> divert(0)
⇒ 出力キューを 0 に切り替え
> undivert(1)
this is queue 3
⇒ 出力キュー 1 の内容を現在の出力キュー (キュー 0) に移動。結果として画面に出力される。キュー 3 に出力した内容がキュー 1 から出てきたことに注意。
include
ファイルを取り込む。ファイルが存在しなかったらエラー。
sinclude
ファイルを取り込む。ファイルが存在しなかったら sinclude がなかったものとして続行する。
syscmd
コマンドを実行する。実行結果はマクロ展開対象とはならない。
esyscmd
コマンドを実行する。実行結果はマクロ展開対象となる。

>> FreeBSDオンラインマニュアル(man) FreeBSD m4(1)

読み方 m4 (UNIXコマンド) [えむふぉー] このエントリーをはてなブックマークに追加

汎用的なマクロ言語プロセッサ。Wikipedia によると、m4 の由来は
"macro" が "m" と残り4文字から成ることに由来する。
とのこと。

m4 は、ブライアン・カーニハン (K&R の K の人。awk の k の人) とデニス・リッチー (K&R の R の人) により設計された。