m4コマンドを利用するためにファイルにマクロの定義とテキスト等の内容を記述します。そして、m4コマンドでそのファイルを指定することで、テキストは定義したマクロに置き換えられていき、その置き換えたテキストを結果として出力するコマンドになります。
m4はUNIXの汎用的なマクロプロセッサで、makeのconfigureスクリプトを生成するautoconfで利用されています。m4コマンドはあまり知られていないコマンドですが、様々な用途に利用できます。
目次
m4の簡単な例
m4は組み込みのマクロdefineを用いて、マクロを定義することができます。
実際の例は以下のようになります。
コマンド例
1 |
m4 test.m4 |
test.m4
1 2 3 4 5 6 7 8 9 |
m4 command test file define(`foo', `hello world') foo define(`bar',`<p>$1</p>') bar(paragraph) <- p tag text |
実行結果
1 2 3 4 5 6 7 8 9 |
m4 command test file hello world <p>paragraph</p> <- p tag text |
m4は基本的に入力された文字列をそのまま出力しますが、組み込みマクロやm4での特殊な文字、定義したマクロの文字列をトークン(token)として認識するとその文字列を別の文字列に置き換えられます。上の例ではdefineのマクロは出力されず、その部分は空行になっています。
これはdefineのマクロ'define(foo',
hello world')'まではm4のマクロなので、取り除かれましたがそのあとの改行コードまでは取り除かれないので、defineの部分は空行になったためです。
空行を表示させたくない場合はdnlという組み込みのマクロを用います。dnlは'Discard to Next Line'の略で改行まで、(改行コードを含めて)出力を行わないマクロになります。
コマンド例
1 |
m4 test2.m4 |
test2.m4
1 2 3 4 5 6 7 8 |
`m4 dnl test file' test text1 define(`foo',`test text1') foo test text2 define(`bar',`test text2')dnl bar |
実行結果
1 2 3 4 5 6 7 |
m4 dnl test file test text1 test text1 test text2 test text2 |
test2.m4で一行目を引用符で囲んでいるのは、囲まない場合、dnlの文字列がトークンとしてm4によって解釈されるためです。
m4のマクロの展開
m4はマクロを展開した後に、さらにマクロを展開していきます。そのため、マクロを定義する場合、無限ループが起こらないようにマクロを定義しなければなりません。
コマンド例
1 |
m4 test3.m4 |
test3.m4
1 2 3 4 5 6 |
macro test define(`hello',`test')dnl define(`world',`file')dnl define(`foo',`hello world')dnl foo |
実行結果
1 2 3 |
macro test test file |
m4の引用符について
m4のデフォルトの引用符はバッククォートとシングルクォートのペアになっています。引用符を用いると、m4が文字列を解釈するときに、引用符のペアを一つ取り除いた文字列として、文字列を扱います。このときに、マクロの展開を行いません。
コマンド例
1 |
m4 test4.m4 |
test4.m4
1 2 3 4 5 6 |
quote test file define(`hello',``hello!'')dnl define(`foo',`hello world')dnl foo `foo' |
実行結果
1 2 3 4 |
quote test file hello! world foo |
m4がdefineのマクロを呼び出したときに、引用符のペアが一つ取り除かれます。
マクロを呼び出したときに、定義後の引用符のペアが1つだけの場合、引用符のない文字列として呼び出した後で、さらにその引用符のない文字列に対して、m4はマクロの展開を行います。
定義後の引用符のペアが2つの場合、defineのマクロを呼び出した後に引用符のペアが1つ残ります。マクロを呼び出したときに、引用符のある文字列として呼び出され、引用符のある文字列をさらに展開して、引用符のペアを取り除いて、そのままの文字列として出力することができます。
また、m4では引用符はchangequoteという組み込みのマクロで変更可能になります。autoconfでのconfigure作成のためのファイルでは、`'の引用符はシェルスクリプト作成の選択として悪い選択なので、別の引用符として[]に変更されています。
このように使用するテキストによって引用符を変更して、m4を使用すると使いやすくなります。
コマンド例
1 |
m4 test5.m4 |
test5.m4
1 2 3 4 5 |
changequote(`[',`]')dnl [m4 changequote test file] define([foo],[hello world])dnl foo |
実行結果
1 2 3 |
m4 changequote test file hello world |
m4のコメント
m4のコメントとして#が利用できます。しかし、このコメントは#以降の文字列を改行までそのまま出力するという意味になります。
プログラミング言語での表示をしない意味でのコメントを用いる場合は、dnlや組み込みマクロのdivertが利用できます。
コマンド例
1 |
m4 test6.m4 |
test6.m4
1 2 3 4 5 6 7 8 9 10 |
m4 comment test #comment line dnl discard mext line divert(-1) no output text define(`foo',`hello world') divert`'dnl foo |
実行結果
1 2 3 4 5 |
m4 comment test #comment line hello world |
組み込みマクロのdivertは数字を指定して出力を切り替えるマクロです。最初に数字を指定したdivertを用いて出力を切り替え、出力切り替えの終わりにもう一度divertを呼び出して、出力を元に戻します。そして、最後にm4が終了するときにdivertを呼び出した数字の順番通りにテキストを出力していきます。
負の数字を指定して利用するとそのテキストは何も出力されません。ただし、divert内のテキストは解釈されているため、負の数字を指定して、マクロを定義する手法はよく使用されます。
負の数字を指定したdivertの中で、#でのコメントは活用するとその行はマクロの展開が行われず、出力もされないため、プログラミング言語でのコメントのように利用できます。
m4のマクロの引数
マクロの引数はシェルの引数と同じように$1,$2,$3のような$記号と数字の組み合わせで参照することができます。
コマンド例
1 |
m4 test7.m4 |
test7.m4
1 2 3 4 |
macro argument test define(`foo',`$1:$2')dnl foo(data,1) |
実行結果
1 2 3 |
macro argument test data:1 |
また、特殊な引数として$#,$*,$@も存在します。
$#は引数の数を、$*は引用符なしで全ての引数の展開を、$@は引用符ありとしてすべての引数の展開をします。
コマンド例
1 |
m4 test8.m4 |
test8.m4
1 2 3 4 5 6 7 8 9 10 11 |
special argument test define(`mark1',`$#')dnl `$#='mark1(aaa,bbb,ccc) define(`hello',`-----')dnl define(`mark2',`$*')dnl $*=mark2(`hello',`world') define(`mark3',`$@')dnl $@=mark3(`hello',`world') |
実行結果
1 2 3 4 5 6 7 |
special argument test $#=3 $*=-----,world $@=hello,world |
m4のマクロの再帰処理
m4のマクロは展開された後、再びマクロが存在するかどうかを確認して、存在する場合には、さらにマクロを展開します。この特徴を利用することで、m4はマクロの再帰処理を行うことができます。
ここで、マクロの再帰処理で用いる組み込みのマクロはifelseとshiftになります。
ifelseの構文は3つあり、マクロの再帰的展開では主に3つ目の構文を利用します。
1 2 3 |
ifelse (comment) ifelse (string-1, string-2, equal, [not-equal]) ifelse (string-1, string-2, equal-1, string-3, string-4, equal-2, …, [not-equal]) |
1つ目は何も出力しないため、コメントとして利用できます。
2つ目は引数の文字列が等しいかどうかで処理を変更できます。
3つ目はstring-1とstring-2が等しい場合はequal-1の処理を、string-3とstring-4が等しい場合はequal-2の処理を、のように続き、最後にどれにも条件が合わない場合の時にnot-equalの処理が行われます。
次に、shiftのマクロは最初の引数を取り除いて、それ以外の引数を出力するマクロになります。
1 |
shift(aaa,bbb,ccc) |
は
1 |
bbb,ccc |
のように出力されます。
elseifとshiftを用いると、マクロの引数に対して再帰処理を行うことができます。以下の例はその再帰処理の例になります。処理は単純で、引数にあるものを-(ハイフン)で結合しています。
コマンド例
1 |
m4 test9.m4 |
test9.m4
1 2 |
define(`foo',`ifelse(`$#',`0',`',`$#',`1',`$1',`$1-foo(shift($@))')')dnl foo(aaa,bbb,ccc) |
実行結果
1 |
aaa-bbb-ccc |
上の例は、ifelseでマクロで呼び出されたの引数の数を確認して、残りの引数の数が1以外なら一つ目の引数を展開してからshiftで引数を減らして再帰処理、残りの引数の数が1、つまり、最後のひとつならば最後の引数を展開して処理を終了します。また、引数が0の場合は何も出力しません。
再帰処理はとても強力な機能で、再帰処理という考え方はとても応用ができるので覚えておいて損はありません。
m4の高速読み込みのためのフローズンファイル(.m4f)について
m4コマンドは複数のファイルを引数に取ることができ、最初に定義ファイルを読み込み、その後にその定義ファイルの定義を利用したファイルを使用することができます。
コマンド例
1 |
m4 common.m4 content.m4 |
common.m4
1 2 3 |
changequote(`[',`]')dnl define([foo],[hello world])dnl define([bar],[HELLO WORLD])dnl` |
content.m4
1 2 |
foo bar |
実行結果
1 2 |
hello world HELLO WORLD |
ただし、定義が記述された共通ファイルが巨大になっていく場合、共通ファイルの読み取りに時間がかかる場合があります。共通ファイルの読み取りはファイルをフローズン状態(froze state)にすることで高速化できます。また、フローズンファイルの拡張子として'.m4f'が利用されます。
フローズンファイル(frozen files)の作成はm4コマンドの-Fオプションで出力ファイル(フローズンファイル)を指定し、引数にフローズンファイルにするファイルを指定します。
コマンド例
1 |
m4 -F common.m4f common.m4 |
フローズンファイルの読み取りは-Rオプションを利用することで読み取ることができます。
コマンド例と実行結果
1 2 3 |
$ m4 -R common.m4f content.m4 hello world HELLO WORLD |
HTMLのテーブルをm4で作成
m4を用いる例として、HTMLのテーブルを作成してみます。
HTMLのテーブルにする表
time(hour) | walk(km) | bicycle(km) | car(km) |
---|---|---|---|
0.0000 | 0.0000 | 0.0000 | 0.0000 |
0.2500 | 1.2500 | 3.7500 | 10.0000 |
0.5000 | 2.5000 | 7.5000 | 20.0000 |
0.7500 | 3.7500 | 11.2500 | 30.0000 |
1.0000 | 5.0000 | 15.0000 | 40.0000 |
1.2500 | 6.2500 | 18.7500 | 50.0000 |
1.5000 | 7.5000 | 22.5000 | 60.0000 |
1.7500 | 8.7500 | 26.2500 | 70.0000 |
2.0000 | 10.0000 | 30.0000 | 80.0000 |
2.2500 | 11.2500 | 33.7500 | 90.0000 |
2.5000 | 12.5000 | 37.5000 | 100.0000 |
2.7500 | 13.7500 | 41.2500 | 110.0000 |
3.0000 | 15.0000 | 45.0000 | 120.0000 |
3.2500 | 16.2500 | 48.7500 | 130.0000 |
3.5000 | 17.5000 | 52.5000 | 140.0000 |
3.7500 | 18.7500 | 56.2500 | 150.0000 |
4.0000 | 20.0000 | 60.0000 | 160.0000 |
4.2500 | 21.2500 | 63.7500 | 170.0000 |
4.5000 | 22.5000 | 67.5000 | 180.0000 |
4.7500 | 23.7500 | 71.2500 | 190.0000 |
5.0000 | 25.0000 | 75.0000 | 200.0000 |
まず、作成するhtmlのテーブルの構造は以下のような形式になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<table> <tr> <th>time(hour)</th> <th>walk(km)</th> <th>bicycle(km)</th> <th>car(km)</th> </tr> <tr> <td>0.0000</td> <td>0.0000</td> <td>0.0000</td> <td>0.0000</td> </tr> <tr> <td>0.2500</td> <td>1.2500</td> <td>3.7500</td> <td>10.0000</td> </tr> ... </table> |
tableの構造はtableタグの中に最初にヘッダー行があり、その後にデータ行が続くような形式になります。
それでは、m4の定義を考えていきます。m4の定義は、再帰処理を用いると単純になります。 また、下の例では、入力のしやすさを考えて、引用符も変更しています。
html_define_table.m4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
changequote(`[',`]')dnl divert(-1) define([TABLE_BEGIN],[<table>]) define([TABLE_HEADER],[<tr> _TABLE_HROW($@) </tr>]) define([_TABLE_HROW],[ifelse([$#],[0],[], [$#],[1],[<th>$1</th>],[<th>$1</th> _TABLE_HROW(shift($@))])]) define([TABLE_ROW],[<tr> _TABLE_DROW($@) </tr>]) define([_TABLE_DROW],[ifelse([$#],[0],, [$#],[1],[<td>$1</td>], [<td>$1</td> _TABLE_DROW(shift($@))])]) define([TABLE_END],[</table>]) divert[]dnl |
ここでは、簡単にユーザが利用するものは、TABLE_BEGIN、TABLE_HEADER、TABLE_ROW、TABLE_ENDのマクロと考えています。また、内部の処理を行うマクロとして、最初にアンダーバーをつけて、_TABLE_HROW、_TABLE_DROWも定義しています。
これで、実際に利用してみます。上の表を作成するためのファイルは、以下のようになります。
table_content.m4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
TABLE_BEGIN TABLE_HEADER(time(hour),walk(km),bicycle(km),car(km)) TABLE_ROW(0.0000,0.0000,0.0000,0.0000) TABLE_ROW(0.2500,1.2500,3.7500,10.0000) TABLE_ROW(0.5000,2.5000,7.5000,20.0000) TABLE_ROW(0.7500,3.7500,11.2500,30.0000) TABLE_ROW(1.0000,5.0000,15.0000,40.0000) TABLE_ROW(1.2500,6.2500,18.7500,50.0000) TABLE_ROW(1.5000,7.5000,22.5000,60.0000) TABLE_ROW(1.7500,8.7500,26.2500,70.0000) TABLE_ROW(2.0000,10.0000,30.0000,80.0000) TABLE_ROW(2.2500,11.2500,33.7500,90.0000) TABLE_ROW(2.5000,12.5000,37.5000,100.0000) TABLE_ROW(2.7500,13.7500,41.2500,110.0000) TABLE_ROW(3.0000,15.0000,45.0000,120.0000) TABLE_ROW(3.2500,16.2500,48.7500,130.0000) TABLE_ROW(3.5000,17.5000,52.5000,140.0000) TABLE_ROW(3.7500,18.7500,56.2500,150.0000) TABLE_ROW(4.0000,20.0000,60.0000,160.0000) TABLE_ROW(4.2500,21.2500,63.7500,170.0000) TABLE_ROW(4.5000,22.5000,67.5000,180.0000) TABLE_ROW(4.7500,23.7500,71.2500,190.0000) TABLE_ROW(5.0000,25.0000,75.0000,200.0000) TABLE_END |
コマンド例と実行結果
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
$ m4 html_table_define.m4 table_content.m4 <table> <tr> <th>time(hour)</th> <th>walk(km)</th> <th>bicycle(km)</th> <th>car(km)</th> </tr> <tr> <td>0.0000</td> <td>0.0000</td> <td>0.0000</td> <td>0.0000</td> </tr> <tr> <td>0.2500</td> <td>1.2500</td> <td>3.7500</td> <td>10.0000</td> </tr> <tr> <td>0.5000</td> <td>2.5000</td> <td>7.5000</td> <td>20.0000</td> </tr> <tr> <td>0.7500</td> <td>3.7500</td> <td>11.2500</td> <td>30.0000</td> </tr> <tr> <td>1.0000</td> <td>5.0000</td> <td>15.0000</td> <td>40.0000</td> </tr> <tr> <td>1.2500</td> <td>6.2500</td> <td>18.7500</td> <td>50.0000</td> </tr> <tr> <td>1.5000</td> <td>7.5000</td> <td>22.5000</td> <td>60.0000</td> </tr> <tr> <td>1.7500</td> <td>8.7500</td> <td>26.2500</td> <td>70.0000</td> </tr> <tr> <td>2.0000</td> <td>10.0000</td> <td>30.0000</td> <td>80.0000</td> </tr> <tr> <td>2.2500</td> <td>11.2500</td> <td>33.7500</td> <td>90.0000</td> </tr> <tr> <td>2.5000</td> <td>12.5000</td> <td>37.5000</td> <td>100.0000</td> </tr> <tr> <td>2.7500</td> <td>13.7500</td> <td>41.2500</td> <td>110.0000</td> </tr> <tr> <td>3.0000</td> <td>15.0000</td> <td>45.0000</td> <td>120.0000</td> </tr> <tr> <td>3.2500</td> <td>16.2500</td> <td>48.7500</td> <td>130.0000</td> </tr> <tr> <td>3.5000</td> <td>17.5000</td> <td>52.5000</td> <td>140.0000</td> </tr> <tr> <td>3.7500</td> <td>18.7500</td> <td>56.2500</td> <td>150.0000</td> </tr> <tr> <td>4.0000</td> <td>20.0000</td> <td>60.0000</td> <td>160.0000</td> </tr> <tr> <td>4.2500</td> <td>21.2500</td> <td>63.7500</td> <td>170.0000</td> </tr> <tr> <td>4.5000</td> <td>22.5000</td> <td>67.5000</td> <td>180.0000</td> </tr> <tr> <td>4.7500</td> <td>23.7500</td> <td>71.2500</td> <td>190.0000</td> </tr> <tr> <td>5.0000</td> <td>25.0000</td> <td>75.0000</td> <td>200.0000</td> </tr> </table> |