### Tip 69: Use a makefile for everything

level: shell user

Part of a series of tips on POSIX and C. Start from the tip intro page, or get 21st Century C, the book based on this series.

A makefile is, for the most part, an organized set of shell scripts. Which is great, because you probably have a lot of silly little scripts floating around. Rather than producing a new one- or two-line script for every little task you have for your project, put them all into a makefile.

This site, for example, is generated via LATEX. Here's the sort of makefile one could write for the process. It's all lies: the actual process is much more involved, but the details wouldn't be relevant for you.

Oh, and if you cut and paste, don't forget that the white space at the head of each line must be a tab, not spaces. Blame POSIX.

all: doc publish

doc:
pdflatex -interaction batchmode $(f).tex publish: scp$(f).pdf $BLOGSERVER  I would use it like this: f=119-tip-make_everything make  because that sets the environment variable f and then calls make with the variable set. This is why sh won't let you you put spaces around the equal sign: the space is how it distinguishes between the assignment and the command. But having set f and run make, the first target, all, gets called, and that depends on doc and publish, which get called in sequence. If I know it's not yet ready to copy out to the world, then I can call make doc and just do that step. I'm sure you could imagine lots of other things one would do with a document (word count, spell check, write to revision control, push revision control out to a remote, make backup) that involves a nontrivial line or three of code; all of that can go into the makefile so you don't have to think about it. ###### How makefiles and shell scripts differ • Shell variables have names in parens. E.g., $(HOME).
• You will need to double your $$s for those odd cases when that's not sufficient: for i in *.c; do cp$$i {i%%.c}.bkup; done
• Every line runs independently, as if in a separate shell. If you try
cd junkdir #do not try this at home.
then you will be a sad puppy. Make will first change in to the directory you are emptying, then Make is done with that subshell and returns to where you were. Then it starts a new subshell and calls rm *, from the directory you are in. On the plus side, Make will delete the erroneous makefile for you. Instead, do it like this: cd junkdir && rm -f * (where the && runs commands in short-circuit sequence just line in C). Or use a backslash to continue the line, though for a case like this I wouldn't trust just a backslash.
• An @ at the head of a line means run the command but don't echo anything to the screen as it happens.
• A - at the head of a line means that if the command returns a nonzero value, keep going anyway. Otherwise, the script halts on the first nonzero return.
push:
@if [ "x$(MSG)" = 'x' ] ; then echo "MSG='whatever, dude.'" make push; fi @test "x$(MSG)" != 'x'
git commit -a  -m "$(MSG)" git svn fetch git svn rebase git svn dcommit  I need a message for each commit, so I do that via another environment variable set on the spot: MSG="This is a commit." make  You can see that I have an if-then statement that prints a reminder if I forget this. Then, the makefile tests to make sure that "x$(MSG)" expands to something besides just 'x', meaning that MSG is not empty. This is a common shell idiom to make up for an idiotic POSIX glitch.