Friday, April 18, 2008

Everybody Makes Mistmakes

There are mistakes I make over and over and over. One of them is forgetting that every command in a make rule is a separate shell.

Make is the tool that makes the most use of shell scripts. Makefiles are full of shell commands. In a make rule, though, every command line is executed by a separate subshell. This won't work:

foo:
for i in *
do
echo $i
done

The good news: you'll get an error message, because only the echo is a shell command by itself. You have to write it like this:

foo:
for i in *; do echo $i; done

Even when you lay it out nicely, you need the semicolons, because the shell has to think it's one line.

foo:
for i in *; \
do \
echo $i; \
done

The bad news is that if it's not a syntax error, you won't see an error message.

But wait. It gets worse. If I start in my home directory, and make with this Makefile:

default:
cd /tmp/foo.d
rm -rf *

One subshell will do the cd, but the rm -rf will be done in my home directory, by a separate subshell.

$ cd
$ make
/tmp
/home/jsh
$ ls
$ # Um. All my files are gone.

I should have written it like this:

default:
cd /tmp/foo.d; rm -rf *

I make this kind of mistake in crontabs, too. Enumerating the times I've made these was a primary motivation for Georg Cantor's invention of transfinite cardinals.

No comments: