Makefileで使える関数の解説は、今回取り上げるshell関数が最後だ。この関数はほかの関数とは異なり、シェルプロセスを起動してそちらで処理を行わせ、その出力を結果として利用する。レシピで行われている処理を明示的に行うようなイメージだ。強力な関数で何かと便利なのでつい使いたくなるが、乱用はしない方が良い。
shell関数
Makefileでは、Makefileに用意されている機能を使って処理を行い、レシピの部分だけがシェルで処理される。基本的にレシピ以外の部分はmakeが処理を行っており、シェルスクリプトのようにシェルコマンドは処理されない。
しかし、Makefileに用意されている機能だけでは記述ができないことがある。シェルやコマンドを使いたいケースがあるのだ。そうした場合に使えるのがshell関数である。同関数は、シェルで言うところのコマンド置換のような動きをする。shell関数の動作を簡単にまとめると次のようになる。
関数 | シンタックス |
---|---|
shell | $(shell シェルコマンド) |
関数 | 内容 |
---|---|
shell | シェルのコマンド置換のように、引数に指定された文字列をシェルコマンドとして実行し、その結果に置き換える。結果に含まれる末尾の改行は削除されるほか、中に含まれている改行はスペースに置き換えられる |
shell関数はシェルを起動し、そちらへ処理を依頼する。そして、その処理結果がshell関数の結果として使われる仕組みだ。ただし、結果に含まれている改行だけは空白に置換され、実行結果の最後に含まれている改行は削除される。
shell関数は呼ばれるたびに新しくシェルを生成する。そのため、再帰的に何度も呼び出されるような書き方の末端に書いてあると、処理が重くなる可能性もある。現在のマシンスペックやシステムコールの実装を考えると、これはもう無視しても良い程度の負荷かなという気もするが、一応心に留めておくと良いだろう。
shell関数を実行してみよう
shell関数はシェルが得意なユーザーであれば扱いやすい関数だ。まず、次のようなMakefileを用意して実行してみよう。
test:
cal
実行結果は以下の通りだ。
% make
cal
7月 2022
日 月 火 水 木 金 土
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
%
レシピに書いてあるコマンドが実行され、その結果が表示されていることがわかる。
次に、同じコマンドの出力をshell関数で実行し、いったん変数に格納してから表示するようにMakefileを書き換えてみる。
cal:= $(shell cal)
test:
echo $(cal)
実行すると次のようになる。
% make
echo 7月 2022 日 月 火 水 木 金 土 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
7月 2022 日 月 火 水 木 金 土 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
%
shell関数の仕様の通り、改行が削除されて空白になっていることがわかる。これがshell関数の基本的な動作だ。
shell関数はどんなときに使うのか
Makefileに用意されている機能でシンプルに記述できるのであれば、その方が良い。だが、それができないことがある。また、レシピに書けばどんなこともできるわけだが、それだとMakefileの仕組み(依存関係と再帰処理)の恩恵が受けられなくなる。そうした場合に、shell関数が必要になる。
例えば、次のコマンドの実行結果を見てみよう。
% ls
20210101 20210223 20210417 20210609 20210801 20210923 20211115
20210102 20210224 20210418 20210610 20210802 20210924 20211116
20210103 20210225 20210419 20210611 20210803 20210925 20211117
20210104 20210226 20210420 20210612 20210804 20210926 20211118
20210105 20210227 20210421 20210613 20210805 20210927 20211119
20210106 20210228 20210422 20210614 20210806 20210928 20211120
20210107 20210301 20210423 20210615 20210807 20210929 20211121
20210108 20210302 20210424 20210616 20210808 20210930 20211122
20210109 20210303 20210425 20210617 20210809 20211001 20211123
20210110 20210304 20210426 20210618 20210810 20211002 20211124
20210111 20210305 20210427 20210619 20210811 20211003 20211125
20210112 20210306 20210428 20210620 20210812 20211004 20211126
20210113 20210307 20210429 20210621 20210813 20211005 20211127
20210114 20210308 20210430 20210622 20210814 20211006 20211128
20210115 20210309 20210501 20210623 20210815 20211007 20211129
20210116 20210310 20210502 20210624 20210816 20211008 20211130
20210117 20210311 20210503 20210625 20210817 20211009 20211201
20210118 20210312 20210504 20210626 20210818 20211010 20211202
20210119 20210313 20210505 20210627 20210819 20211011 20211203
20210120 20210314 20210506 20210628 20210820 20211012 20211204
20210121 20210315 20210507 20210629 20210821 20211013 20211205
20210122 20210316 20210508 20210630 20210822 20211014 20211206
20210123 20210317 20210509 20210701 20210823 20211015 20211207
20210124 20210318 20210510 20210702 20210824 20211016 20211208
20210125 20210319 20210511 20210703 20210825 20211017 20211209
20210126 20210320 20210512 20210704 20210826 20211018 20211210
20210127 20210321 20210513 20210705 20210827 20211019 20211211
20210128 20210322 20210514 20210706 20210828 20211020 20211212
20210129 20210323 20210515 20210707 20210829 20211021 20211213
20210130 20210324 20210516 20210708 20210830 20211022 20211214
20210131 20210325 20210517 20210709 20210831 20211023 20211215
20210201 20210326 20210518 20210710 20210901 20211024 20211216
20210202 20210327 20210519 20210711 20210902 20211025 20211217
20210203 20210328 20210520 20210712 20210903 20211026 20211218
20210204 20210329 20210521 20210713 20210904 20211027 20211219
20210205 20210330 20210522 20210714 20210905 20211028 20211220
20210206 20210331 20210523 20210715 20210906 20211029 20211221
20210207 20210401 20210524 20210716 20210907 20211030 20211222
20210208 20210402 20210525 20210717 20210908 20211031 20211223
20210209 20210403 20210526 20210718 20210909 20211101 20211224
20210210 20210404 20210527 20210719 20210910 20211102 20211225
20210211 20210405 20210528 20210720 20210911 20211103 20211226
20210212 20210406 20210529 20210721 20210912 20211104 20211227
20210213 20210407 20210530 20210722 20210913 20211105 20211228
20210214 20210408 20210531 20210723 20210914 20211106 20211229
20210215 20210409 20210601 20210724 20210915 20211107 20211230
20210216 20210410 20210602 20210725 20210916 20211108 20211231
20210217 20210411 20210603 20210726 20210917 20211109 Makefile
20210218 20210412 20210604 20210727 20210918 20211110
20210219 20210413 20210605 20210728 20210919 20211111
20210220 20210414 20210606 20210729 20210920 20211112
20210221 20210415 20210607 20210730 20210921 20211113
20210222 20210416 20210608 20210731 20210922 20211114
%
このディレクトリには、2021年の日付がディレクトリとして作成されている。各ディレクトリ以下には、それぞれ必要なデータが配置されている。
ここで、指定した期間のディレクトリをディレクトリリストとして取得して処理を行いたいとしよう。「make start=20210701 end=20210707」のように期間を指定するイメージだ。ここでshell関数を利用する。
例えばディレクトリリストを取得するのに、次のようなMakefileを記述する。
dirs:= $(shell \
ls |\
grep ^20 |\
awk ' \
$(start)<=$$1 && $$1<=$(end) \
{ \
print \
} \
' \
)
test:
@echo $(dirs)
「ls」と「grep」でディレクトリの一覧を取得し、awkコマンドで指定された日付の範囲にあるディレクトリだけを抽出している。これをshell関数経由で取得してリストとして変数に代入するという処理を行っている。
実行すると次のようになる。
実行結果その1
% make start=20210701 end=20210707
20210701 20210702 20210703 20210704 20210705 20210706 20210707
%
指定する日付を変更すれば、次のように出力される内容も変化する。
実行結果その2
% make start=20211010 end=20211120
20211010 20211011 20211012 20211013 20211014 20211015 20211016 20211017 20211018 20211019 20211020 20211021 20211022 20211023 20211024 20211025 20211026 20211027 20211028 20211029 20211030 20211031 20211101 20211102 20211103 20211104 20211105 20211106 20211107 20211108 20211109 20211110 20211111 20211112 20211113 20211114 20211115 20211116 20211117 20211118 20211119 20211120
%
shell関数を使うと、こういったことをシンプルに記述できるようになる。当然、shell関数を使うだけではなくシェルのスキルも必要になるが、知っているのと知っていないのとでは大きな違いだ。
関数を知り、関数を使いこなす
これまでに取り上げてきた関数によって、ワイルドカード展開、文字列の操作、ファイルの操作、条件付き展開、繰り返し展開、ファイルの読み書き、関数の定義、変数種類の確認、makeの終了、そしてシェルコマンドの実行ができるようになったはずだ。
そもそもMakefile自体は、ビルド用のツールとして生まれたものだが、用意されている機能を組み合わせて考えると、これ自体をプログラミング言語と言っても良さそうなくらいのボリュームにはなっている。
Makefileは汎用的に使えて便利なツールだ。本連載で取り上げてきた関数は、GNU makeで実装されている全ての関数というわけではないのだが、とりあえず紹介したものは押さえておいていただきたい。