ひがきの日記

2009-05-09

シェルスクリプトの真髄

部屋の掃除をしていたら、昔の UNIX MAGAZINE のコピーが出てきた。*1

職場で Unix シェルスクリプトの講師をしていたときに、みんなに紹介したもの。

…… 私が初めて UNIX マシンや C 言語を触りだしたころのことです。 プログラミングの練習ということで、 「テキストファイルの行を逆順に出力するプログラムを作れ」という 課題を出されました。…… ある人はこれを UNIX のコマンドのみで実現しました。……
#!/bin/sh
cat -n "$@" | sort -nr | cut -c 8-
この方法の優れたところは、最初に出力を得るまでが高速だという点です。…… さらに、UNIX のコマンドを流用しているため安定度が高いという点も見逃せません。…… また、巨大な入力が与えられた場合を考えてみてください。……

*1UNIX MAGAZINE 2003.5, ASCII, 特集 プログラミング入門 6, 今泉貴史

2009-02-18

make の使い方

これも昔書いたもの。

make で何ができるのか?


make の魂

Unix の世界ではソースファイルが命。

ソースファイルさえあれば実行モジュールは消えても、make しなおせばよい。

オブジェクトファイルや実行ファイルは版管理しない。
版管理されたソースファイルを make すれば、その版の実行ファイルができる。


世界で一番簡単な make の使い方

ソースファイルを用意する。

$ cat hello.c
#include <stdio.h>

int main()
{
    printf("hello world.\n");
    return 0;
}

さっそく make を使ってみる。

$ make hello
cc     hello.c   -o hello
$ ./hello
hello world.
$ make hello
make: `hello' is up to date.

とっても簡単だ。


世界で一番簡単な makefile

普通は makefile という名前のファイルに make への指示を書いておく。

$ cat Makefile
hello:

make してみよう。

$ rm hello

$ make
cc     hello.c   -o hello
$ ./hello
hello world.
$ make
make: `hello' is up to date.

とっても簡単だ。


かなり初歩的な makefile

もう少し普通に makefile を書いてみる。

foo.c と bar.c から foobar を make する。
foo.c は foo.h と bar.h を参照し、bar.c は bar.h を参照する。

$ cat Makefile
foobar: foo.o bar.o
	${CC} ${CFLAGS} ${LDFLAGS} -o foobar foo.o bar.o

foo.o: foo.c foo.h bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c foo.c

bar.o: bar.c bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c bar.c

":" の前がターゲット名。
":" の後ろが依存するファイル群 (コンポーネント)。

例えば、ターゲット foobarfoo.obar.o に依存する。
foo.obar.o のどちらかが foobar よりも新しければ作りなおす。

ターゲットの依存関係の次の行には、そのターゲットを作るためのアクションを書く。

アクション行は、TAB で始める。
行頭が TAB でないとアクションとは見做さない。
アクション行は複数書ける。

make はカレントディレクトリから makefile または Makefile を読み込む。
そして makefile の最初に出てくるターゲットを make する。
上記の例を明示的に指示すると "make -f Makefile foobar" となる。


変数

makefile にも変数 (マクロ) は使える。

シェルの要領で変数を定義できる。
シェルと違って = の前後に空白があっても OKay.

参照するときは $ を付け、 { } または ( ) で囲む。
同様の記述で環境変数を参照することも可能。

$ cat Makefile
OBJS = foo.o bar.o

foobar: ${OBJS}
	${CC} ${CFLAGS} ${LDFLAGS} -o foobar ${OBJS}

foo.o: foo.c foo.h bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c foo.c

bar.o: bar.c bar.h
	${CC} ${CPPFLAGS} ${CFLAGS} -c bar.c

変数名は大文字で書くのが習慣。*1

CC, CFLAGS, LDFLAGS などは make があらかじめ用意している変数
make -p で確認できる。


サフィックスルール

「ファイルごと」ではなく「拡張子ごと」にアクションを定義できる。

これで .c の数だけアクションを書かずに済む。

$ cat Makefile
OBJS =  foo.o bar.o

foobar:  ${OBJS}
	${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${OBJS}

.c.o:
	${CC} ${CPPFLAGS} ${CFLAGS} -c $<

foo.o: foo.c foo.h bar.h
bar.o: bar.c bar.h

ターゲットにはサフィックスルールで、.c から .o を作ることを指示する。

$@$< はアクションに使える特殊なマクロ

$@
ターゲット
$<
コンポーネント
$*
コンポーネント (拡張子抜き)
独自のサフィックスルール

まずルールに登場するサフィックスをターゲット .SUFFIXES に定義する。

実際のルールは既存のサフィックスルールと同様に定義する。

$ cat Makefile
.SUFFIXES:
.SUFFIXES: .out .o .c .sqc

    ...

.sqc.c:
	db2 prep $< bindfile

    ...

初めに .SUFFIXES をクリアしてから定義する。
クリアしないと既存の .SUFFIXES への追加になる。 *2

既存の .SUFFIXESmake -p で確認できる。


もう少し洗練された makefile

上記の初歩的な (foo.c と bar.c を使う) makefile を洗練させてみる。

$ cat Makefile
OBJS = foo.o bar.o

foobar: ${OBJS}
	${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${OBJS}

foo.o: foo.c foo.h bar.h
bar.o: bar.c bar.h

現在 使用していなくても CFLAGS, LDFLAGS などのマクロを参照するのは重要。
例えばデバックオプションを指定して make するとき、いちいち makefile を変更する必要がなくなる。

$ make 'CFLAGS=-g'
cc -g   -c -o foo.o foo.c
cc -g   -c -o bar.o bar.c
cc -g  -o foobar foo.o bar.o

ダミーターゲット

ターゲットそのものを作るわけではなく、一連の処理へのエントリを作る。


install

普通 /bin や /usr/local/bin/ などのディレクトリに、いきなりターゲットを作ったりしない。

ターゲットをソースファイルと同じディレクトリに作った後、(テストした後) make install でターゲットを指定のディレクトリにコピーする。

$ cat Makefile
    ...
install: 
	${INSTALL_PROGRAM} ${TARGET} ${DESTDIR}/${TARGET}
    ...

clean

全ターゲットを削除するために make clean を使う。

$ cat Makefile
    ...
clean: 
	rm -f ${TARGET} *.o
    ...

all

ひとつの makefile で複数のターゲットを作成する場合、ターゲット all を使う。

t1, t2, t3 を作る場合、以下のような makefile を用意する。

$ cat Makefile
all: t1 t2 t3

t1: 
	...
t2: 
	...
t3: 
	...

make とだけ打つと all をターゲットに make されて、 t1, t2, t3 が make される。
普通 make all とタイプすることはない。

all は作られないので、毎回 all を make しようとするが、 t1, t2, t3 は必要なときだけ make される。


階層構造

コンパイルすべきソースファイルが複数のディレクトリ存在するとき、ひとつの makefile で別のディレクトリのソースファイルをコンパイルしてはいけない。

以下のディレクトリ構造なら、makefile はそれぞれのディレクトリごとにみっつ作る。

libsrc にある makefile はこれまで通り。
自分のディレクトリにあるターゲットだけ責任を持って make する。

上位層にある makefile は、libsrc を make するよう指示する。*3

$ cat Makefile
all: make_lib make_src

make_lib:
	(cd lib; make)

make_src:
	(cd src; make)

同様に installclean も下位の make を呼び出せるようにする。

上位層の makefile で設定した変数は、下位層の make には引き継がれない。
渡したい変数は明示する。

$ cat Makefile
all: make_lib make_src

make_lib:
	(cd lib; make "CFLAGS=${CFLAGS}")

make_src:
	(cd src; make "CFLAGS=${CFLAGS}")

アクションはシェルスクリプト

make はルールに基づいて再生成が必要だと判断すると、アクションの各行を変数 SHELL で指定したシェルで処理する。

アクション行の変数を評価してから、それを表示しシェルを起動する。

SHELL には普通 /bin/sh が設定される。

実行した結果 ($?) が 0 でなければ、make は処理を中断する。

コマンドの前に "-" を書くと、エラーが起きても処理を中断しない。

コマンドの前に "@" を書くと、アクション行を評価した結果を表示せずに実行する。

$ cat Makefile
target:
	echo "成功すれば次のアクションへ"
	-echo "失敗しても次のアクションへ"
	@echo "実行するコマンドを表示しない"

行ごとにシェルを起動するので、行をまたいでシェル変数を渡すことはできない。
行末が "\" なら、次のアクションを連結して、ひとつのアクションと見做す。

シェル変数make に評価させないためには "$""$" で escape して "$$" と表記する。

$ cat Makefile
TARGETS =  foo bar

install: ${TARGETS}
	for t in ${TARGETS}; do \
	  ${INSTALL_PROGRAM} $$t ${DESTDIR}/$$t; \
	done

*1マクロだし

*2:この例ではクリアせずに追加するだけでも意味は同じだった。

*3:サブシェルにする意味あるのかなぁ?