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
.PHONYand 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.