5    プログラムにおける m4 マクロの使用

この章では,m4 マクロ・プリプロセッサについて説明します。 この m4 マクロ・プリプロセッサは,ソース・ファイルの最初の部分に m4 マクロ定義を設定することにより,ユーザのマクロ定義を可能にするフロント・エンド・フィルタです。m4 プリプロセッサは,プログラム・ソース・ファイルにも,文書ソース・ファイルにも使用することができます。

この章では,マクロについての次の情報について説明します。

5.1    マクロの使用

マクロを使用すると,大量の内容を 1 語または 2 語で代用することができるため,プログラミングや入力作業が容易になります。 ソース・ファイルのマクロ呼び出しには,次のフォーマットを使用します。

name [ ( arg1[ , arg2 ] ) ]

たとえば,何度か同じメッセージを表示する C プログラムがある場合には,次のように,printf 文をコーディングすることができます。

printf("\nThese %d files are in %s:\n",cnt,dir);
 

プログラムの改良に伴なって,メッセージの内容を変更することにした場合,メッセージの各インスタンスを編集する必要があります。 次のようにマクロを定義すると,大量の作業を省略することができます。

define(filmsg,`printf("\nThese %d files are in %s:\n",$1,$2)')
 

このメッセージを出力するすべての場所で,マクロを次のように使用します。

filmsg(cnt,dir);
 

このように指定されていれば,メッセージを 1 箇所で編集するだけですべての表示が変更されます。

マクロ定義は,記号名 (トークン) と,それに置き代わる文字列によって構成されます。 トークンは,英字または下線で始まる英数字 (英文字,数字,および下線) の文字列で,英数字以外の文字 (句読点またはホワイト・スペース) で区切られます。 たとえば,N12N はどちらもトークンですが,A+B はトークンではありません。m4 を使用してファイルを処理すると,マクロが認識されるたびにマクロがその定義と置換されます。 シンボリック・ネームをテキストと置換するだけではなく,m4 は,次のような動作を実行することもできます。

m4 プログラムは,ファイルの各トークンを読み取り,トークンがマクロ名であるかどうか調べます。 他のトークンに埋め込まれたマクロ名は認識されません。 たとえば,m4 は,N12 にトークン N が含まれているとは解釈しません。 トークンがマクロ名の場合,m4 はそれを定義されたテキストに置換し,結果として生じた文字列を入力に戻し,再走査を実行します。

このように,マクロ展開は再帰的です。 マクロ定義には,どんなに深くネストした他のマクロでもインクルードすることができます。 引数を付けてマクロを呼び出すこともできます。 この場合,引数はまとめられ,定義されたテキストを再走査する前に,定義テキストの適切な位置に代入されます。

m4 プリプロセッサは標準の UNIX フィルタです。 標準入力,またはリクエストされた入力ファイルからの入力を受け入れ,その出力を標準出力に書き込みます。 次に,正しい m4 の使用例を示します。

% grep -v '#include' file1 file2 | m4 > outfile
% m4 file1 file2 | cc
 

m4 プログラムは,各引数を順番に処理します。 引数が全くないか,または引数がマイナス記号 ( - ) である場合,m4 は入力ファイルとして標準入力から読み取ります。

5.2    マクロの定義

m4 に実装された約 20 個の組み込みマクロの 1 つである,define コマンドでマクロ定義を作成します。 たとえば次のように定義します。

define(N,100)
 

開き (左) カッコとワード define の間にスペースを入れてはなりません。

このマクロ定義によって,トークン N は処理されたファイル内のどこに現れても 100 に置換されます。 定義テキストは,どんなテキストでもかまいませんが,テキストにカッコが含まれる場合を除きます。 この場合は,対応していないカッコを引用符で囲って保護しない限り,開き (左) カッコの数と閉じ (右) カッコの数が一致していなければなりません。 引用についての詳細は,5.2.1 項を参照してください。

組み込みマクロとユーザ定義マクロは,組み込みマクロの一部が処理の状態を変更することを除いて,同じ方法で動作します。 組み込みマクロの一覧については,5.3 節を参照してください。

マクロを他のマクロとして定義することができます。 たとえば,次のようになります。

define(N,100)
define(M,N)
 

この例では,M および N100 になるように定義しています。 後で N の定義を変更し,新しい値を割り当てた場合,M は,ユーザが N に与えた新しい値ではなく,100 の値を保持します。m4 プリプロセッサが可能な限り早い時点でマクロ名をそれらの定義テキストに展開するため,M の値は N の値に置き換えられません。 全体の結果は,M に関する限り,最初から define(M,100) のように定義した場合と同じになります。M の値を N の値に置き換える場合は,次のように定義の順序を入れ替えてください。

define(M,N)
define(N,100)
 

この時点で,M は文字列 N であると定義されています。 後で M の値が要求されると,MN に置換され,N の値は再走査され,その時点の N の値に置換されます。

define コマンドで作成したマクロ定義は,閉じカッコの次の文字を削除しません。 たとえば,次のように定義します。

Now is the time for all good persons.
define(N,100)
Testing N definition.
 

上記の例の結果は,次のようになります。

Now is the time for all good persons.
 
Testing 100 definition.
 

空白行が define マクロを含んだ行の最後に改行文字がある結果として出力されます。 組み込みの dnl マクロは,次の改行文字まで後続する文字を次の改行文字を含めてすべて削除します。 空白行を削除するには,このマクロを使用してください。 たとえば,次のように指定します。

Now is the time for all good persons.
define(N,100)dnl
Testing N definition.
 

上記の例の結果は,次のようになります。

Now is the time for all good persons.
Testing 100 definition.
 

この節では,次の情報について説明します。

5.2.1    引用符の使用

define マクロの引数の展開を遅らせるには,マクロを対応する引用符で囲みます。 引用符として使用される文字の省略時の設定は,左右の一重引用符 (` および ') ですが,組み込みの changequote マクロを使用すれば,別の文字を指定することができます (5.3 節を参照)。 引用符で囲まれたテキストはすぐには展開されませんが,引用符自体は削除されます。 引用符で囲まれた値は,引用符を削除された状態の文字列です。 次の例を参照してください。

define(N,100)
define(M,`N')
 

引数が収集されると,N のまわりの引用符は削除されます。 引用符を使用すると,M100 ではなく,文字列 N として定義されます。 この例では,M の値として N の値が置換されています。 したがってこの方法も,5.2 節に示した次の定義と同じ結果を得るための 1 つの方法です。

define(M,N)
define(N,100)
 

一般規則として,m4 が何かを評価するときには,必ず 1 レベルの引用符文字を削除します。 このことは外部マクロでも同じです。 たとえば,ワード define を出力に表示するには,次のように引用符で文字を囲んでください。

`define' = 1
 

m4 が引用符を処理するので,ネストされたマクロには注意する必要があります。 次の例を考えてみましょう。

define(dog,canine)
define(cat,animal chased by `dog')
define(mouse,animal chased by cat)
 

cat の定義を処理した時点では,引用符で囲まれているために dog は直ちに canine に置換されませんが,mouse が処理される場合には,cat (animal chased by dog) の定義が使用されます。 この時点で,dog は引用符で囲まれていないため,mouse の定義は animal chased by animal chased by canine になります。 上記の例が,infile というファイルに含まれている場合は,次のようになります。

% cat infile
define(dog,canine)
define(cat,animal chased by `dog')
define(mouse,animal chased by cat)
 
dog
cat
mouse
% m4 infile
canine
animal chased by canine
animal chased by animal chased by canine

既存のマクロを再定義する場合は,最初の引数 (マクロ名) を次のように引用符で囲む必要があります。

define(N,100)

.
.
.
define(`N',200)  

引用符で囲まれていない場合には,2 番目の define マクロは N を検出して認識し,値を置換して次のような結果になります。

define(100,200)
 

m4 プログラムでは,名前だけが定義できて数値は定義できないため,この文は無視されます。

5.2.2    マクロ引数

最も簡単なマクロ処理の形式は,これまでの説明にあるように,1 つの文字列を別の (固定した) 文字列に置換することです。 ただし,所定のマクロによって異なった場所で異なった結果を得るために,マクロに引数を付けることができます。 引数がマクロの置き換えテキスト (その定義の 2 番目の引数) 内で使用される位置を示すには,n 番目の引数を示す $n 記号を使用してください。 たとえば,記号 $1 はマクロの最初の引数を参照します。 マクロが使用された場合,m4 は,記号を示された引数の値に置換します。 次の例を参照してください。

define(bump,$1=$1+1)

.
.
.
bump(x);  

この例では,m4bump(x) 文を x=x+1 に置換します。

マクロには必要なだけ引数を付けることができますが,$n 記号 ($1 から $9 まで) を使用してアクセスできるのは 9 個の引数だけです。 9 番目の引数以降の引数にアクセスするには,shift マクロを使用してください。 shift マクロは,最初の引数を手放し,残りの引数を $n 記号 (2 番目の引数を $1 に,3 番目の引数を $2 に) に再び割り当てます。shift マクロを 1 回以上使用すると,マクロに使用されるすべての引数にアクセスすることができます。

記号 $0 はマクロ名を返します。 引数が指定されなかった場合は,空文字列に置換されます。 このため,次のように引数を連結してマクロを定義することができます。

define(cat,$1$2$3$4$5$6$7$8$9)

.
.
.
cat(x,y,z)  

この例では,cat(x,y,z) 文が xyz に置換されます。 対応する引数が提供されないため,この例の $4$9 の引数は null になります。

マクロを走査する時点で,m4 プログラムは,引用符で囲まれていない前の空白,タブ,および引数の中の改行文字を破棄しますが,他のすべてのホワイト・スペースは保持します。 次の例を参照してください。

define(a,     "$1 $2$3")

.
.
.
a(b, c, d)  

この例では,a マクロを b cd となるように展開しています。 ただし,define マクロでは,改行文字は有効です。 次の例を参照してください。

define(a,$1
$2$3)

.
.
.
a(b,c,d)  

この 2 番目の例では,a マクロは次のように展開されます。

b
cd 
 

マクロ引数はコンマで区切られます。 引数にコンマが含まれている場合には,引数の途中で終了していると誤解されないように,カッコを使用してコンマを含む引数を囲んでください。 たとえば,次の文の引数は 2 つだけです。

define(a, (b,c))
 

最初の引数は a で,2 番目の引数は (b,c) です。 引数内に単一のカッコを指定する場合には,一重引用符で囲んでください。

define(a,b`)'c)
 

この例では,b)c が 2 番目の引数になります。

5.3    他の m4 マクロの使用

m4 プログラムは,定義済みのマクロ (組み込みマクロ) のセットを提供しています。表 5-1 ではこれらのマクロのすべてをリストし,概略を説明しています。

以下の項では,大部分のマクロとその使用法について,詳細に説明します。

表 5-1:  組み込みの m4 マクロ

マクロ 説明
changecom(l,r) 左右のコメント文字を l および r で表された文字に変更する。 2 つの文字は異ならなければならない。
changequote(l,r) 左右の引用符文字を l および r で表された文字に変更する。 2 つの文字は異ならなければならない。
decr(n) n-1 の値を返す。
define(name,replacement) replacement の値を持つ name という名称の新しいマクロを定義する。
defn(name) 引用符で囲まれた name の定義を返す。
divert(n) 出力ストリームを番号 n の一時ファイルに変更。
divnum 現在アクティブな一時ファイルの番号を返す。
dnl 改行文字までのテキストを削除する。
dumpdef(`name'[,`name'...]) 指定したマクロの名称および現在の定義をプリントする。
errprint(str) str を標準エラー・ファイルにプリントする。
eval(expr) expr を 32 ビット算術式として評価する。
ifdef(`name',arg1,arg2) マクロ name が定義されている場合は arg1 を返し,そうでない場合は arg2 を返す。
ifelse(str1,str2,arg1,arg2) 文字列 str1 および str2 を比較する。ifelse は,それらが一致していた場合は arg1 の値を,そうでない場合は arg2 の値を返す。
include(file) sinclude(file) file の内容を返す。 ファイルにアクセスすることができない場合は,sinclude マクロはエラーを報告しない。
incr(n) n+1 の値を返す。
index(str1,str2) 文字列 str1 内の str2 が開始する文字位置を返し,str1str2 を含まない場合は -1 を返す。
len(str) dlen(str) str の文字数を返す。dlen マクロは,国際化文字の 2 バイト表現を含む文字列で動作する。
m4exit(code) code をリターン・コードとして m4 を終了する。
m4wrap(name) 他のすべての処理を完了した後で,終了前にマクロ name を実行する。
maketemp(strXXXXXstr) 引数文字列のリテラル文字列 XXXXX をカレントのプロセス ID に置換し,ユニークなファイル名を作成する。
popdef(name) name の現在の定義を,pushdef マクロでセーブされた定義に置換する。
pushdef(name,replacement) name の現在の定義をセーブし,define と同じ方法で namereplacement になるように定義する。
shift(param_list) パラメータ・リストの位置を左に 1 つシフトし,元のリストの最初の要素を削除する。
substr(string,pos,len) 文字位置 pos で始まり,len 文字長である string の部分文字列を返す。
syscmd(command) 指定されたシステム・コマンドをリターン値なしで実行する。
sysval 直前に使用された syscmd マクロからリターン・コードを獲得する。
traceoff(macro_list) リスト内のすべてのマクロのトレースをオフにする。macro_list が指定されなかった場合は,すべてのトレースをオフにする。
traceon(name) 指定されたマクロのトレースをオンにする。name が指定されなかった場合は,すべてのマクロのトレースをオンにする。
translit(string,set1,set2) string の中の set1 のすべての文字を set2 の対応する文字に置換する。
undefine(`name') `name' で指定されたマクロ定義を取り消す。
undivert(n,n[,n...]) 指定された一時ファイルの内容を,現在の一時ファイルに追加する。

5.3.1    コメント文字の変更

m4 プログラムにコメントを付ける場合には,コメント文字でコメント行を区切る必要があります。 省略時の値の左コメント文字は,番号記号 ( # ) です。 省略時の値の右コメント文字は改行文字です。 これらの文字で問題がある場合は,組み込みの changecom マクロを次のように使用してください。

changecom({,})
 

この例では左右の中カッコを新しいコメント文字にしています。 元のコメント文字に戻すには,次のように changecom を使用してください。

changecom(#,
)
 

引数なしで changecom を使用すると,コメントは無効になります。

5.3.2    引用符文字の変更

引用符文字の省略時の値は左右の一重引用符 (` および ') です。 これらの文字で問題がある場合は,組み込みの changequote マクロで引用符文字を,次のように変更してください。

changequote([,])
 

この例では,左右の大カッコ用の新しい引用符で囲む文字が作成されます。 元の引用符文字に戻すには,次のように引数なしで changequote を使用してください。

changequote
 

5.3.3    マクロ定義の取り消し

undefine マクロはマクロ定義を取り消します。 次の例を参照してください。

undefine(`N')
 

この例では,N の定義が取り消されています。 定義を取り消すマクロ名は引用符で囲まなければなりません。undefine を使用すれば組み込みのマクロを取り消すこともできますが,組み込みのマクロを取り消すと,マクロを復旧して使用することはできなくなります。

5.3.4    定義されたマクロのチェック

組み込みの ifdef マクロは,現在マクロが定義されているかどうか確認します。ifdef マクロは 3 つの引数を受け入れます。 最初の引数が定義されている場合,ifdef の値は 2 番目の引数です。 最初の引数が定義されていない場合,ifdef の値は 3 番目の引数です。 3 番目の引数がない場合,ifdef の値は空になります。

5.3.5    整数演算の使用

m4 プログラムは,整数のみの演算を可能にする,次の組み込み関数を提供しています。

incr 数値引数を 1 つ増分させる
decr 数値引数を 1 つ減少させる
eval 算術式を評価する

たとえば,その値がいつでも N より 1 大きくなるような,変数 N1 を作成することができます。

define(N,100)
define(N1,`incr(N)')
 

eval 関数を使用して,次の演算子を含む式を評価することができます。 優先順位の高い順にリストされています。

カッコを使用して,必要な位置で演算をグループ化してください。 式のすべてのオペランドは 1>0 などの真の関係の数値は 1 で,偽は 0 (ゼロ) です。eval の精度は 32 ビットです。 たとえば,M2==N+1 として定義するには,次のように eval を使用してください。

define(N,3)
define(M,`eval(2 == N+1)')
 

テキストが単純で,マクロ名のインスタンスを含まない場合を除いて,マクロを定義するテキストは引用符文字で囲んでください。

5.3.6    ファイルの操作

入力に新しいファイルを挿入するには,組み込みの include マクロを次のように使用してください。

include(myfile)
 

この例では,include コマンドの場所に myfile の内容が挿入されています。 組み込まれたファイルが読み込まれると,m4 は,それが最初の入力ファイルの一部であるかのように,そのファイルの中にマクロがあるか走査します。

include マクロで指定されたファイルにアクセスできない場合には,致命的なエラーが発生します。 エラーを回避するには,代わりのフォーマットである sinclude (silent inlcude) を使用してください。 指定されたファイルにアクセスできない場合にも sinclude マクロは,エラーなしで続きます。

5.3.7    出力のリダイレクト

処理中に m4 の出力を一時ファイルにリダイレクトし,収集したデータをコマンドで出力することができます。m4 プログラムは,1〜9 までの番号を付けて最大 9 個の一時ファイルを保守することができます。 出力をリダイレクトするには,以下の例のように divert マクロを使用します。

divert(4)
 

このコマンドが出現すると,m4 はその出力を一時ファイル 4 の終わりまで書き出し始めます。 出力点を 1〜9 以外の一時ファイルにリダイレクトすると,m4 プログラムは出力を破棄します。 この機能を利用して,m4 は入力ファイルの一部を割合することができます。 出力を標準出力ストリームに返すには,divert(0) または引数なしで divert を使用してください。

処理の終了時に,m4 は,すべてのリダイレクトされた出力を,標準出力ストリームに書き出し,番号順に一時ファイルから読み込んで,一時ファイルを削除します。 処理の終了前にも,番号順にすべての一時ファイルから情報を取り出したい場合には,引数なしで組み込みの undivert マクロを使用します。 特定の順番で一時ファイルを選んで取り出す場合には,引数を付けて undivert を使用します。undivert を使用した場合,m4 は回収した一時ファイルを破棄し,回収した情報に対してはマクロを探索しません。

undivert の値は,切り替えられたテキストではありません。

組み込みの divnum マクロは,現在使用中の一時ファイルの番号を返します。 divert マクロで出力ファイルを変更しない場合,m4 は,すべての出力を一時ファイル 0 (ゼロ) にいれます。

5.3.8    プログラムでのシステム・プログラムの使用

組み込みの syscmd マクロを使用すると,プログラム内から任意のオペレーティング・システムのプログラムを実行することができます。 システム・コマンドが情報を返す場合は,その情報は syscmd マクロの値になり,そうでない場合はマクロの値は空になります。 たとえば次のように使用します。

syscmd(date)
 

5.3.9    固有のファイル名の使用

プログラムから固有のファイル名を作成するには,組み込みの maketemp マクロを使用してください。 マクロの引数にリテラル文字列 XXXXX がある場合,m4XXXXX を現在の処理プロセス ID に置換します。 たとえば次のように指定します。

maketemp(myfileXXXXX)
 

現在のプロセス ID が 23498 である場合は,この例は myfile23498 を返します。 この文字列を使用して一時ファイルに名称を付けることができます。

5.3.10    条件式の使用

組み込みの ifelse マクロは条件付きのテストを実行します。 最も簡単な形式は次のような形式です。

ifelse(a,b,c,d)
 

この例では,2 つの文字列 a および b を比較します。a および b が同一である場合,ifelse は文字列 c を返します。 異なる場合は,文字列 d を返します。 たとえば,compare というマクロを次のように定義して 2 つの文字列を比較することができます。 2 つの文字列が同じである場合は yes を,異なる場合は no を返します。

define(compare, `ifelse($1,$2,yes,no)')
 

引用符を付けることによって,ifelse の評価が早すぎないようにします。 4 番目の引数を省略すると,空として処理されます。

ifelse マクロは引数をいくつでも付けることができます。 したがって,多岐選択決定機能の限定された形式を提供します。 たとえば次のようになります。

ifelse(a,b,c,d,e,f,g)
 

この文は次のフラグメントと論理的に同じです。

if(a == b) x = c;
else if(d == e) x = f;
else  x = g;
return(x);
 

最後の引数が省略された場合は,その結果は空になります。

5.3.11    文字列操作

組み込みの len マクロは,その引数を構成する文字列のバイト長を返します。 たとえば,len(abcdef) は 6 で,len((a,b)) は 5 になります。

組み込みの dlen マクロは,文字列で表示可能な文字列の長さを返します。 国際化の使用環境では,2 バイト・コードを 1 文字として表示するものもあります。 したがって,文字列に 2 バイトの国際文字コードが含まれる場合,dlen の結果は len の結果と異なります。

組み込みの substr マクロは,指定された文字列 (最初の引数) から部分文字列 (2 番目の引数によって指定された文字位置で始まる) を返します。 3 番目の引数は,返される部分文字列のバイトの長さを指定します。 たとえば次のように指定します。

substr(Krazy Kat,6,5)
 

この例では,文字列 "Krazy Kat" の文字位置 6 から始まる 3 つの部分文字列 "Kat" を返します。 文字列の最初の文字は位置 0 (ゼロ) にあります。 3 番目の引数が省略されるか,あるいはこの例のように文字列の長さが 3 番目の引数が必要とする長さに満たない場合,残りの文字列が返されます。

組み込みの index マクロは,部分文字列 (2 番目の引数) が始まる文字列 (最初の引数) 内でバイト位置すなわちインデックスを返します。 部分文字列がない場合,index は -1 を返します。 substr を使用すると,文字列の起点は 0 (ゼロ) になります。 次の例を参照してください。

index(Krazy Kat,Kat)
 

この例は 6 を返します。

組み込みの translit マクロは,1 対 1 の文字置換すなわち字訳を実行します。 最初の引数は,処理される文字列です。 2 番目と 3 番目の引数は文字のリストです。 文字列で見つけることのできる 2 番目の引数からの文字の各インスタンスは,3 番目の引数からの対応する文字に置換されます。 次の例を参照してください。

translit(the quick brown fox jumps over the lazy dog,aeiou,AEIOU)
 

この例では次のように返されます。

thE qUIck brOwn fOx jUmps OvEr thE lAzy dOg
 

3 番目の引数が 2 番目の引数より短い場合,3 番目の引数に存在しない 2 番目の引数に対する文字は削除されます。 3 番目の引数がない場合,2 番目の引数に表示されているすべての文字が削除されます。

注意

substrindex,および translit マクロは,1 バイトと 2 バイトの表示可能文字の区別を行わないので,国際化コードを使用している場合に予期しない結果を返すことがあります。

5.3.12    表示

組み込みの errprint マクロは,引数を標準エラー・ファイルに書き込みます。 たとえば次のように指定します。

errprint (`error')
 

組み込みの dumpdef マクロは,現在の名前および引数として指定された項目の定義をダンプします。 名前は必ず引用符で囲まなければなりません。 引数を指定しない場合,dumpdef はすべての現在の名前および定義を表示します。dumpdef マクロは,標準エラー・ファイルに書き込みます。