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
% 
  • 実行結果その2

    実行結果その2

shell関数を使うと、こういったことをシンプルに記述できるようになる。当然、shell関数を使うだけではなくシェルのスキルも必要になるが、知っているのと知っていないのとでは大きな違いだ。

関数を知り、関数を使いこなす

これまでに取り上げてきた関数によって、ワイルドカード展開、文字列の操作、ファイルの操作、条件付き展開、繰り返し展開、ファイルの読み書き、関数の定義、変数種類の確認、makeの終了、そしてシェルコマンドの実行ができるようになったはずだ。

そもそもMakefile自体は、ビルド用のツールとして生まれたものだが、用意されている機能を組み合わせて考えると、これ自体をプログラミング言語と言っても良さそうなくらいのボリュームにはなっている。

Makefileは汎用的に使えて便利なツールだ。本連載で取り上げてきた関数は、GNU makeで実装されている全ての関数というわけではないのだが、とりあえず紹介したものは押さえておいていただきたい。

参考