I strongly suggest to use a script and not a makefile.
The reasons are, in short:
- adhering to common practice. or the principle of least astonishment.
- maintainability and extensibility.
- and the fact that makefiles are designed to automate a build process, not to automate sysadmin tasks.
It is not inherently "wrong" to use a makefile, it has it's benefits, but also a lot of caveats. Whatever you decide you should know the trade offs.
Let me elaborate.
adhering to common practice or the principle of least astonishment
When I see a script I assume it automates something. When I see a makefile I assume it builds something. When I work in your office and I want to add a user and I look in the documentation and it says to run a makefile I will be confused at first. Will the makefile build the tool that I then use to add a user? I will have to examine the makefile to make sure it really does add a user.
maintainability and extensibility
Change happens. Systems change. Requirements change. Tools will need to be adapted. Here are some caveats I can think of when trying to adapt a makefile that is used to automate a sysadmin task.
In no particular order:
You have to prepend every line in the recipe with a tab (see makefile rule syntax defintion). Maybe this is just my personal annoyance. It can be overcome with a good editor which can be configured to not replace the leading tab with spaces in makefiles. workaround possible with .RECIPEPREFIX but then you are again breaking common expectation.
You can't (easily) handle arguments in makefiles. This will make it very hard to add arguments like
--no-action. See here for more explanation: https://stackoverflow.com/a/45003119/360899
In addition to above: you can't customize the argument handling error messages. In other words: you can't write meaningful error messages if the user gave incorrect arguments
You can't (easily) extract common code to functions. There are "canned recipes". But I'd rather not.
You can't (easily) write multiline shell script constructs because make executes every line in the recipe for itself. For example
if [ condition ]; then commands; fi or
for elements; do commands; done has to be written in one line. workaround possible using .ONESHELL
Also, because of the above, you can't (easily) set a flag in one line and act on the flag in a line further down. Because each line stands on its own. workaround as above.
Other arcane makefile quirks. For example:
Bonus problem: you can't put a makefile in
~/bin. you will need to write a wrapper script anyway.
makefiles are designed to automate a build process, not to automate sysadmin tasks
Using makefiles to automate system tasks would be like using git, instead of rsync, for system backup.
Make is a powerful tool with a lot of quirks to automate build processes. Git is a powerful tool with a lot of quirks to do code versioning. The quirks are tolerable if using the tool is helping a lot. But if there are other tools better meant for the task at hand then why work around the quirks from a tool meant to do something else.
The benefit of a makefile, that i can think of, would be that you do not need to write boilerplate for argument handling. Compare:
@echo commands that do the things for useradd
@echo commands that do the things for userdel
if [ $# == 0 ]; then
echo "need argument: useradd or userdel"
if [ "$1" == "useradd" ]; then
echo "commands that do the things for useradd"
elif [ "$1" == "userdel" ]; then
echo "commands that do the things for userdel"
echo "unknown argument: $1"
$ make userdell
make: *** No rule to make target 'userdell'. Stop.
$ ./script.sh userdell
unknown argument: userdell
you can't customize the error messages.
$ touch userdel
$ make userdel
make: 'userdel' is up to date.
if a file named
userdel exists, for whatever reason, make will correctly deduce that there is nothing that needs to be done.