Replacing make with a Shell Script for Running Your Project's Tasks
make is a great tool but it has certain characteristics that make it unfriendly for making long commands easier to run.
I’m a big fan of making things easier for myself, especially if I can do it in
such a way that requires not making any compromises. If you’re using a
Makefile
as a glorified project specific aliases files then in my opinion a
shell script suites that job better.
For example, running docker-compose exec web flask db reset --with-testdb
is
kind of long and it’s a type of command that I don’t want to run by hand all
the time while developing my app.
You could use make
to wrap this up into a make db-reset
command and that
would work no problem but what happens when you want to create a generic make flask
command , such as being able to run make flask db reset --with-testdb
,
make flask routes
or 8 other make flask <insert_command>
commands?
Suddenly everything falls apart because make
doesn’t support ${@}
which
means it can get a bit inconvenient to pass arguments into a make task in a
natural way.
You’d have to run make flask ARGS="routes"
or ARGS="routes" make flask
and
then remember to add support for $(ARGS)
to every task that uses ARGS
.
Alternatively you could create separate make commands but this falls apart when you want to run arbitrary sub-commands of another command that may or may not have flags. You’d have to create a new command for every combo of sub-commands and flags.
But the above issues are easy to solve with a shell script.
In this video we’ll go over how to design a shell script where you can define
functions that automatically become commands, and then you can create shortcuts
like run flask db reset --with-testdb
or run flask routes
very easily. The
script itself will end up being less lines than a Makefile
and IMO even
easier to add new tasks too.
We’ll also go over how to create namespaced commands like run ci:test
and how
to define private functions that don’t get exposed as commands.
It’ll also work in any environment that can run shell scripts (native Linux / macOS / WSL on Windows) and it won’t require installing any dependencies since you already have a shell available (sh, bash, etc.).
Technically you could even use Python, Ruby or other scripting language instead of Bash. It’s up to you.
# Demo Video
Timestamps
- 0:49 – A basic Makefile, using
.PHONY
and controlling output - 5:12 – The main reason why I switched from a Makefile to a shell script
- 7:35 – Taking a look at a run script that replaces a Makefile
- 8:14 – A quick comparison between a shell function and make task
- 9:19 – Using
$@
to pass arguments from a command into the script - 10:27 – Creating shell functions without arguments
- 11:07 – Namespacing shell functions with a colon to group up commands
- 11:31 – Creating private functions to use as helpers in public commands
- 13:14 – A couple of useful functions you might want in a web application
- 14:51 – Figuring out this shell script pattern from a Taskfile project on GitHub
- 15:34 – Creating a run alias so you don’t need to type
./
every time - 16:24 – Adding in the idea of having private functions
- 16:54 – Auto generating a help menu with printf, compgen, grep and cat
- 20:34 – Using time or eval to convert arguments into runnable functions
- 22:48 – Any questions? I’m happy to answer them in the comments below
Reference Links
- https://github.com/nickjj/docker-flask-example
- https://gist.github.com/nickjj/a6e4f33b4588a6e231a8b9374ce6fe58 (Makefile)
- https://github.com/adriancooney/Taskfile
What do you think about this pattern, will you start using it? Let me know below.