Makefileで外部のプログラムやコマンドを実行した結果を変数に取り込む方法を調べて結構苦労したのでメモとして残しておく。
結論としては\$(shell ), ` `とあと \$(eval )あたりを駆使する。
簡単な例
例としてdate +%M:%S:%Nで時刻を取得してそれを変数VALに取り込む例を考える。
VAL := $(shell date +%M:%S:%N)
run :
@echo $(VAL)
VAL := `date +%M:%S:%N`
run :
@echo $(VAL)
> make run
52:14:280298700
> make run
52:59:203304400
このように\$(shell )か` `のどちらかを使えば良いだけである。これだけだと大した話ではないのだが、状況が少し込み入って来るといろいろと面倒にな事がある。
変数の受け渡しと複数回の参照
まずMakefile内で設定した変数を外部プログラムに環境変数として渡したい場合を考える。ここでは渡したい変数としてMSGを設定する。環境変数として外部に渡す為にMSGの前にexportを付ける。またMakefileのrun内のechoでのVALの表示回数を3回に増やす。実行する外部プログラムは以下の様にやはり現在時刻の取得としてその後ろに\$MSGの値を付けたシェルスクリプトにする。
#!/bin/sh
TIME=`date +%M:%S:%N`
echo "$TIME : $MSG"
export MSG := Hello!
VAL := $(shell ./now.sh)
run :
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
export MSG := Hello!
VAL := `./now.sh`
run :
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
これらを実行するとどうなるか。。。
> make run
26:32:118294900 :
26:32:118294900 :
26:32:118294900 :
> make run
26:27:190915200 : Hello!
26:27:223885900 : Hello!
26:27:256983200 : Hello!
このようにまず、\$(shell )を使った結果3ではexportを使っているのに\$MSGが環境変数として./now.shに渡っていない。` `を使用した結果4では\$MSGはきちんと渡っているが、echo \$VALでVALの値を表示する度に./now.shが実行されて違う値となってしまう。
まとめるとそれぞれ以下の欠点があると言える。
- \$(shell)ではMakefile内でexportで設定した変数が渡らない。
- ` `はコマンドそのものがマクロとして代入され参照される度にそのコマンドが実行されてしまう。
以下にそれぞれ解決策を提示する。
解決策
- \$(shell)内で環境変数の設定をするshシェルコマンドを追加。
MSG := Hello!
VAL := $(shell export MSG=$(MSG); ./now.sh)
run :
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
MSG := Hello!
VAL := $(shell env MSG=$(MSG) ./now.sh)
run :
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
- いったん結果をファイルにダンプしてからcatでその内容を表示する。
export MSG := Hello!
VAL := `cat dump.txt`
run :
./now.sh > dump.txt
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
export MSG := Hello!
run :
./now.sh > dulmlp.txt
$(eval VAL := `cat dump.txt`)
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
export MSG := Hello!
run :
./now.sh > dump.txt
$(eval VAL := $(shell cat dump.txt))
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
> make run
42:37:754889100 : Hello!
42:37:754889100 : Hello!
42:37:754889100 : Hello!
> make run
42:42:755746800 : Hello!
42:42:755746800 : Hello!
42:42:755746800 : Hello!
どれもきちんとMSGも表示されかつVALの値も同じとなった。
例4の解決策は値のファイルへのダンプおよびVALへの値の取得を別ルールとして分離すると分かりやすくて良いかもしれない。
export MSG := Hello!
#VAL := `cat dump.txt`
run : get_VAL
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
get_VAL :
./now.sh > dump.txt
$(eval VAL := $(shell cat dump.txt))
# $(eval VAL := `cat dump.txt`)
又、以下も可能であった。
export MSG := Hello!
DUMP := `./now.sh > dump.txt`
VAL := $(shell cat dump.txt)
run :
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
尚、ファイルにダンプせずできないものかと以下試してみたがうまくは行かなかった。
export MSG := Hello!
NOW := `./now.sh`
VAL := $(shell echo $(NOW))
run :
@echo $(VAL)
@echo $(VAL)
@echo $(VAL)
> make run
04:25:965031000 :
04:25:965031000 :
04:25:965031000 :
実行は一回だが$(shell)内での実行とみなされやはりMSGが渡っていない。
\$(shell export)を使ったやり方の方が優れている気もするがそうとも言えない。渡す変数が大量になってくると書くのが大変になって入れ忘れなどのミスを誘発しやすくなる。その場合は` `を使った方法にした方が良いだろう。` `の方はどうしてもファイルダンプしなくてはならない。それが難しい場合は欠点となる。
いずれにせよ、上記のような込み入った状況以外の単純な状況ではどちらでも大差はない。
###まとめ
変数への代入方法 | 変数の受け渡し | |
---|---|---|
$(shell) | コマンドの実行結果の値を代入。 | 自動では環境変数として渡されない。シェルコマンドで明示的に記述する。 |
` ` | コマンド自体をマクロとして代入。 | Makefileのexportで環境変数として自動的に渡される。 |
$(eval) | 実行部分で変数を動的に設定。 | ー |
###参考記事
makeでシェルの結果を利用する
Qiita : GNU make変数の伝播についてまとめ
Qiita : Makefileで実行時に変数代入したい
Qiita : makefile 動的に変数に代入 共通して使う
Qiita : シェル変数と環境変数の違いをコマンドラインで確認する
Qiita : 今さら聞けない!環境変数とシェル変数の違いと定義方法
Qiita : Linux 環境変数有効範囲
【 env 】コマンド――環境変数を指定してコマンドを実行する
【 export 】コマンド――環境変数やシェル変数を設定する