Makefileにタスクをまとめる

makeとMakefileには多くの機能があるのだが、その多くはプログラムやドキュメントのビルドのような、特定の目的で役立つものが多い。本連載では、プログラミングではなくビジネスツールとしてmakeとMakefileを使っていく方法を取り上げていきたいので、そういった機能に関する説明は後に回すことにする。

これまで、makeとMakefileの最も基本的な機能を取り上げてきた。以下のMakefileは、今回利用しようと思っている最も基本的なMakefileの書き方だ。

ターゲット1:
    コマンド
    コマンド
    コマンド
    コマンド

ターゲット2:
    コマンド
    コマンド
    コマンド
    コマンド

ターゲット3:
    コマンド
    コマンド
    コマンド
    コマンド

Makefileでは行頭から始まり、「:」までの記述を「ターゲット」と呼ぶことが多い。作業をまとめるという観点では、次のようにタスクとして考えるとわかりやすいと思う。

タスク名1:
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド

タスク名2:
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド

タスク名3:
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド

次のように、具体的なタスク名で書いておくと、どのようなことをしようとしているのかわかりやすいだろう。

売上データの取得:
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド

売上データの整理:
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド

整理済み売上データのアップロード:
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド
    そのタスクをこなすコマンド

タスク(ターゲット)の記述は、日本語でも構わないのでわかりやすいタスク(ターゲット)名を付けておくとよい。そもそも、仕事をまとめておく方法としてMakefileを使おうという発想のビジネスハックなので、わかりやすいことは大切だ。

そしてここで重要なのは、先ほどの例では「そのタスクをこなすコマンド」という書き方をしたが、ここはワンライナー(1行)になっているということだ。コマンドは1行で完結する必要があり、複数行に渡ってシェルスクリプトのように書くことはできない。

タスク名1:
    ワンライナーコマンド
    ワンライナーコマンド
    ワンライナーコマンド

しかし、実際にはシェルスクリプトのような書き方をしているMakefileが多い。これは一見するとシェルスクリプトが書いてあるように見せかけているものの、実際にはワンライナーで書いてあるだけだ。ワンライナーをあたかもシェルスクリプトであるかのように書いている。

実は、この部分は、シェルスクリプトを書くよりも高いスキルが要求される部分だったりする。ワンライナーで書く必要がある上に、Makefile自体のエスケープの書き方を合わせる必要があるためだ。あまりに難しくなってくるとシェルスクリプトを書いた方がマシだと感じるのではないかと思うのだが、書けるようになっておいて損をするものではないので、どうやって書けばよいかを説明していこう。

まずは処理をシェルスクリプトで書く

いきなりワンライナーで処理を書くのは難しいだろう。まずは、シェルスクリプトで行いたい処理を書いて実行できることを確認する。

例えば、次のシェルスクリプト「create2022dirs.sh」は2022年の日付のディレクトリを作成するというものだ。シェルスクリプトを実行すると、「20220101」から「20221231」まで365個のディレクトリが作成される。日付名のディレクトリを作成してそこにその日の売上データなどをデプロイしていくというのはよくあるファイル配置の一つであり、このスクリプトはその下準備を行うものだ。

#!/bin/sh

sday="20220101"
fmt="%Y%m%d"

for i in $(seq 0 364)
do
    mkdir $(date -d "$sday +$i day" +"$fmt")
done

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

  • create_2022_dirs.shの実行結果

    create_2022_dirs.shの実行結果

「20220101」から「20221231」まで365個のディレクトリが作成されているのがおわかりいただけるだろう。

シェルスクリプト自体は短いものだが、dateコマンドの実行やコマンド置換の使用例としても参考になるものだ。いろいろと応用が効くと思う。

いったんデータのデプロイを始めたらディレクトリを削除するということはまずないのだが、例えば「2022」というディレクトリに基となる全データを配置しておき、使用時に必要に応じてcreate_2022_dirs.shを実行してディレクトリを作成し、そこにファイルを分割して配置する……といった使い方をすることがある。その場合、作業が終わったら日付ディレクトリは削除しておきたい。

ということで、create_2022_dirs.shとは逆に作成したディレクトリを削除するシェルスクリプトを「delete_2022_dirs.sh」として作成しておく。次のような感じだ。

#!/bin/sh

sday="20220101"
fmt="%Y%m%d"

for i in $(seq 0 364)
do
    rm -r $(date -d "$sday +$i day" +"$fmt")
done

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

  • delete_2022_dirs.shの実行結果

    delete_2022_dirs.shの実行結果

実行すると、create_2022_dirs.shで作成したディレクトリが一斉に削除されるはずだ。

シェルスクリプトをワンライナーにしてMakefileへ書き込む

では、先ほど作成したシェルスクリプトをワンライナーへ置き換えてMakefileへ整える。次の通りだ。

#!/bin/sh

sday=20220101
fmt=%Y%m%d

create_2022_dirs:
    # 2022年の日付ディレクトリを作成
    for i in $$(seq 0 364);                 \
    do                          \
        mkdir $$(date -d "${sday} +$$i day" +${fmt});   \
    done

delete_2022_dirs:
    # 2022年の日付ディレクトリを消去
    for i in $$(seq 0 364);                 \
    do                          \
        rm -r $$(date -d "${sday} +$$i day" +${fmt});   \
    done

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

  • make create_2022_dirsの実行結果

    make create_2022_dirsの実行結果

  • make delete_2022_dirsの実行結果

    make delete_2022_dirsの実行結果

先ほどと同じように処理が実行されていることがわかるだろう

Makefileにおけるワンライナー

もう一度、さっきのMakefileを見てみよう。

#!/bin/sh

sday=20220101
fmt=%Y%m%d

create_2022_dirs:
    # 2022年の日付ディレクトリを作成
    for i in $$(seq 0 364);                 \
    do                          \
        mkdir $$(date -d "${sday} +$$i day" +${fmt});   \
    done

delete_2022_dirs:
    # 2022年の日付ディレクトリを消去
    for i in $$(seq 0 364);                 \
    do                          \
        rm -r $$(date -d "${sday} +$$i day" +${fmt});   \
    done

行末が「\」で終わっている行は実際には1行につながる。つまり、実際には、Makefileでシェルスクリプト風に書いてある部分はワンライナーをそれっぽく書き換えただけだ。

Makefileでシェルスクリプト風に書いてある部分1

    for i in $$(seq 0 364);                 \
    do                          \
        mkdir $$(date -d "${sday} +$$i day" +${fmt});   \
    done

上記と同等のワンライナー

for i in $$(seq 0 364); do mkdir $$(date -d "${sday} +$$i day" +${fmt}); done

Makefileでシェルスクリプト風に書いてある部分2

    for i in $$(seq 0 364);                 \
    do                          \
        rm -r $$(date -d "${sday} +$$i day" +${fmt});   \
    done

上記と同等のワンライナー

for i in $$(seq 0 364); do rm -r $$(date -d "${sday} +$$i day" +${fmt}); done

そしてこれだけではシェルスクリプトの置き換えはできないので、補助的に次の表記を行っている。

Makefileにおける変数

sday=20220101
fmt=%Y%m%d

この記述は、Makefileの変数だ。

シェルは変数が使えるし、コマンド置換などの処理もできる。これと同じ機能はMakefileにも用意されている。特に変数の表記はMakefileとシェルはほぼ同じなのだ。このため、Makefileの変数なのかシェルの変数なのかを明確に区別しなければならない。Makefileでは「${変数名}」という表記は処理前に展開されるので、シェルの変数としては使えない。その場合には「$」をエスケープして「$$」という書き方をする必要がある。例えば、「$$i」という表記は処理前に「$i」となり、「$i」がシェルに渡されてそこで変数として展開されて処理される、ということになる。

この辺りは次回詳しく説明するが、とにかくシェルスクリプトはMakefileのワンライナーに書くことが可能であり、Makefileに書いておくと何かと便利だ。次回以降、この書き方に慣れるべく、どのように書き換えればよいかを説明していこう。

参考