December 11, 2020

shell 関数

shell はシェルコマンドを実行して出力を返します。

$(shell command)

出力は変数に代入できますし、空白区切りであればリストとしても扱えます。

ls := $(shell ls)

ls:
	echo $(ls)
	echo $(firstword $(ls))
$ make ls
echo Makefile fuga hoge list.txt
Makefile fuga hoge list.txt
echo Makefile
Makefile

出力の改行(newline)はスペースに置換され、リストとして扱われます。

cat := $(shell cat list.txt)

cat:
	echo $(cat)
	echo $(lastword $(cat))
$ cat list.txt
hoge
fuga
vaa
$ make cat
echo hoge fuga vaa
hoge fuga vaa
echo vaa
vaa

!= 代入演算子

変数への代入には != 代入演算子が使えます。!=:= と同じく一度だけ評価されます。

op != date +%s

op1:
	echo $(op)
	sleep 3
	echo $(op)

op2:
	echo $(op)
$ make op1 op2
echo 1607849554
1607849554
sleep 3
echo 1607849554
1607849554
echo 1607849554
16078495543

ただ、Makefile が読まれた際の処理量が増えると make が重くなるため、後述の variable ?= $(shell ...) または = を使うことが多いです。

.SHELLSTATUS

shell 関数または != 代入演算子が使用された後、その終了ステータスは .SHELLSTATUS 変数に格納されます。

shell-status:
	@echo $(shell pwd)
	echo $(.SHELLSTATUS)
	@echo $(shell exit 127)
	echo $(.SHELLSTATUS)
$ make shell-status
/work/example/011
echo 0
0

echo 127
127

$$ エスケープ

ルールのコマンド部分に $(shell ...) を書くと、Makefile が読まれたタイミングで評価され、ルールの実行前に展開されてしまいます。 実行時まで評価を遅延させたい場合は $$ でエスケープすることで、通常のサブシェルとして実行できます。

subshell:
	ls -lsa $$(pwd)
make subshell
ls -lsa $(pwd)
total 8
0 drwxrwxr-x  6 root root 192 Dec 13 08:46 .
0 drwxrwxr-x 14 root root 448 Dec 13 08:43 ..
4 -rw-rw-r--  1 root root 411 Dec 13 09:01 Makefile
0 -rw-r--r--  1 root root   0 Dec 13 08:44 fuga
0 -rw-r--r--  1 root root   0 Dec 13 08:44 hoge
4 -rw-r--r--  1 root root  13 Dec 13 08:46 list.txt

これは単純な $ のエスケープなので、awk '{ print $$1 } といったことも当然できます。

評価を遅延する

実行に時間がかかるため本当に必要なときだけ実行させたいコマンドには、必要になったときに評価される ?== 代入演算子が使えます。

PROFILE :=
REGION :=
BUCKET := s3://example.com/

aws := aws --profile=$(PROFILE) --region=$(REGION)
s3ls ?= $(shell $(aws) s3 ls $(BUCKET) | awk '{print $$4}')

s3ls:
	echo $(s3ls)
$ make s3ls PROFILE=... REGION=... BUCKET=...
echo  Makefile fuga hoge list.txt
Makefile fuga hoge list.txt

リンク