●条件付き展開を行う関数

○条件付き展開を行う関数

前回はファイルの操作に関する関数を紹介した。今回は、関数としてはちょっと風変わりな「条件付き展開」を行う関数を取り上げる。制御構文としてのキーワードと似たワードが使われているので混乱しやすい部分だ。ここを整理しておこう。

GNU makeでは、次の3つの関数が条件付きの展開を行う関数となっている。

それぞれ意味は次の通りだ。

「if」という関数はMakefileにおいてフローを制御するif系の条件式に似ているが、まったくの別物だ。if関数はフローそのものを変えることはできない。「展開する対象を切り替えられる」というのがif関数の機能だ。

if関数、or関数、and関数については、説明を読んだだけではよくわからないのではないかと思う。これらの関数については、実際にMakefileを作成して動作を追っていく方が理解が早い。

○if関数

if関数の動作を調べるために次のようなMakefileを用意した。

v1="value1"

v2="value2"

v3="value3"

e=

all: list-values test1 test2 test3 test4 test5

list-values:

@echo v1='"'$(v1)'"'

@echo v2='"'$(v2)'"'

@echo v3='"'$(v3)'"'

@echo e=

@echo

test1:

@echo '$$(if $$(v1),"真","偽")'

@echo ' -> '$(if $(v1),"真","偽")

@echo

test2:

@echo '$$(if $$(e),"真","偽")'

@echo ' -> '$(if $(e),"真","偽")

@echo

test3:

@echo '$$(if $$(e),"真")'

@echo ' -> '$(if $(e),"真")

@echo

test4:

@echo '$$(if $$(v1),$$(v2),$$(v3))'

@echo ' -> '$(if $(v1),$(v2),$(v3))

@echo

test5:

@echo '$$(if $$(e),$$(v2),$$(v3))'

@echo ' -> '$(if $(e),$(v2),$(v3))

@echo

実行すると次のようになる。

% make

v1="value1"

v2="value2"

v3="value3"

e=

$(if $(v1),"真","偽")

-> 真

$(if $(e),"真","偽")

-> 偽

$(if $(e),"真")

->

$(if $(v1),$(v2),$(v3))

-> value2

$(if $(e),$(v2),$(v3))

-> value3

%

実行結果

それぞれ動きを追っていこう。まず、以下がif関数の基本的な使い方となる。

$(if $(v1),"真","偽")

if関数は、第1引数が空文字列であるかどうかで挙動を変える。第1引数が空文字列でなければ第2引数を評価し、その結果をif関数の結果とする。第1引数が空文字列であれば第3引数を評価し、その結果をif関数の結果とする。つまり、第1引数が空文字列であるかどうかによって評価する対象を変える(展開する変数を変える)のだ。この挙動は、if制御構文に似ており、if関数という名前になるのも納得である。

先ほどの例だと、$(v1)には文字列が代入されているので真となり、第2引数がif関数に置き換わる。

次は第1引数を空文字列にした場合だ。

$(if $(e),"真","偽")

つまり、こちらは第1引数が空文字列なので第3引数が評価されif関数と置き換わる。if的に表現するなら、変数が空文字列ではないならば「真」、空文字列であれば「偽」ということになる。

第2引数までしか書いてない場合、第1引数が空文字列だった場合(偽だった場合)、if関数は何も行わない。

$(if $(e),"真")

ここまでは第2引数と第3引数に文字列を書いてあるが、次のように変数を書いておくこともできる。

$(if $(v1),$(v2),$(v3))

第2引数や第3引数が変数であれば、変数が展開された結果がif関数に置き換わる。これがif関数の動きだ。変数が設定されているかどうかで展開する対象を変えることができる。デフォルト値の設定などに利用することができるわけだ。

●or関数/and関数

○or関数

or関数の処理は、最初はわかりにくいかもしれない。挙動としてはor演算的な動きをするのだが、makeの変数展開で利用できるようなorであり、動きを見てみるのが理解への早道だ。

ここでは次のようなMakefileを用意した。

v1="value1"

v2="value2"

v3="value3"

e=

all: list-values test1 test2 test3 test4

list-values:

@echo v1='"'$(v1)'"'

@echo v2='"'$(v2)'"'

@echo v3='"'$(v3)'"'

@echo e=

@echo

test1:

@echo '$$(or $$(v1),$$(v2),$$(v3),$$(e))'

@echo ' -> '$(or $(v1),$(v2),$(v3),$(e))

@echo

test2:

@echo '$$(or $$(e),$$(v1),$$(v2),$$(v3))'

@echo ' -> '$(or $(e),$(v1),$(v2),$(v3))

@echo

test3:

@echo '$$(or $$(e),$$(e),$$(e),$$(v1))'

@echo ' -> '$(or $(e),$(e),$(e),$(v1))

@echo

test4:

@echo '$$(or $$(e),$$(e),$$(e),$$(e))'

@echo ' -> '$(or $(e),$(e),$(e),$(e))

@echo

実行すると次のようになる。

% make

v1="value1"

v2="value2"

v3="value3"

e=

$(or $(v1),$(v2),$(v3),$(e))

-> value1

$(or $(e),$(v1),$(v2),$(v3))

-> value1

$(or $(e),$(e),$(e),$(v1))

-> value1

$(or $(e),$(e),$(e),$(e))

->

%

動きを追ってみよう。まず、次のような書き方をしてみる。

$(or $(v1),$(v2),$(v3),$(e))

この場合、or関数は$(v1)の評価結果に置き換わる。それは$(v1)が空文字列ではないからだ。この段階でor関数の条件を満たすため、以降は評価せずに$(v1)を展開したものに置換される。

では次のように第1引数を空文字列にしてみる。

$(or $(e),$(v1),$(v2),$(v3))

この場合も、or関数は$(v1)を評価した結果に置き換わる。最初に第1引数の$(e)を評価するが、これは空文字列なのでor関数の条件を満たさない。このため、次の第2引数の評価へ向かう。第2引数は$(v1)で空文字列ではないので、or関数の条件を満たし、この結果へ置き換わる。

次のように書いてみよう。

$(or $(e),$(e),$(e),$(v1))

もうわかると思うが、第1引数、第2引数、第3引数と評価が行われるが、全て空文字列なのでor関数の条件を満たさない。最後の第4引数が空文字ではないので、第4引数の展開結果に置き換わる。

最後は次の書き方だ。

$(or $(e),$(e),$(e),$(e))

これは第1引数から第4引数まで全てが空文字列だ。このため、全ての条件を満たすことはなく、or関数は空文字列に置き換わる。これがor関数の動作だ。

複数ある候補の中から最初に空文字列ではなかったものを取り出す、それがor関数の使い道ということになる。

○and関数

and関数はor関数以上にわかりにくい。しかし、動きを見れば一発で理解できるように思う。実際にどのように動作するのかを見てみよう。ここでは次のようなMakefileを作成した。

v1="value1"

v2="value2"

v3="value3"

e=

all: list-values test1 test2 test3 test4 test5

list-values:

@echo v1='"'$(v1)'"'

@echo v2='"'$(v2)'"'

@echo v3='"'$(v3)'"'

@echo e=

@echo

test1:

@echo '$$(and $$(v1),$$(v2),$$(v3),$$(e))'

@echo ' -> '$(and $(v1),$(v2),$(v3),$(e))

@echo

test2:

@echo '$$(and $$(e),$$(v1),$$(v2),$$(v3))'

@echo ' -> '$(and $(e),$(v1),$(v2),$(v3))

@echo

test3:

@echo '$$(and $$(v1),$$(v2),$$(e),$$(v3))'

@echo ' -> '$(and $(v1),$(v2),$(e),$(v3))

@echo

test4:

@echo '$$(and $$(v1),$$(v2),$$(v3))'

@echo ' -> '$(and $(v1),$(v2),$(v3))

@echo

test5:

@echo '$$(and $$(v3),$$(v2),$$(v1))'

@echo ' -> '$(and $(v3),$(v2),$(v1))

@echo

実行すると次のようになる。

% make

v1="value1"

v2="value2"

v3="value3"

e=

$(and $(v1),$(v2),$(v3),$(e))

->

$(and $(e),$(v1),$(v2),$(v3))

->

$(and $(v1),$(v2),$(e),$(v3))

->

$(and $(v1),$(v2),$(v3))

-> value3

$(and $(v3),$(v2),$(v1))

-> value1

%

まず、次の3つの書き方を見てみよう。

$(and $(v1),$(v2),$(v3),$(e))

$(and $(e),$(v1),$(v2),$(v3))

$(and $(v1),$(v2),$(e),$(v3))

これらは全て空文字列に展開される。なぜなら、どれも引数の中に1つ空文字列が含まれているからだ。and関数は引数にどれか1つでも空文字列が含まれていた場合、空文字列を見つけた時点で評価をやめ、and関数を空文字列へ置き換える。これがand関数だ。

次のように引数に空文字列を含めないとする。

$(and $(v1),$(v2),$(v3))

この場合、and関数は最後の引数を展開した結果に置き換わる。上記の場合であればand関数は$(v3)に置き換わることになる。and関数はなかなか使いどころが難しいのだが、「1つでも空文字列が含まれている場合には空文字列になる」という動きを使いたいケースもあるのだ。

○条件付き展開

条件付き展開は関数の中でわかりやすいとは言い難いし、多用すると読みにくくなる。処理は、できれば上から下へ流れるように書いたほうが後から読んだときに理解しやすい。条件付き展開を使わないとか、使わずにシンプルに書けるならそちらの方が良いだろう。

しかし、これまでにも説明してきたように、既存のGNU makeのMakefileではすでにこの関数が使われている。したがって、どのように機能するかを知らないと、そうしたMakefileを読むことができないのだ。

タスクを整理するような目的でMakefileを使う場合はこうした機能は必要ないことが多いが、基本的な動きくらいは把握しておこう。ちゃんとした動きが知りたくなったら、マニュアルを読んだりして思い出せば良いのではないだろうか。

○参考

Make - GNU Project - Free Software Foundation

GNU make

GNU make [PDF]