30

I love to type bash scripts, but if I prepare multiple tools, project's root directory is filled with so many shell scripts. That's why I prefer using Makefile.

Makefile is good. However I want to build my makefiles just as regular bash scripts.

Eg:

Think that I wrote a bash script very quickly with the following content:

#!/bin/bash
echo "hello"
cd ~
do-some-work.sh my-parameter

I can run this script with $ ./my-important-task.sh.

If I wanted to move that script into makefile, I should do the following:

SHELL := /bin/bash 

my-important-task: 
    echo "hello" ;\
    cd ~ ;\
    do-some-work.sh my-parameter

but I want the following:

my-important-task: 
    [[copy and paste the my-important-task.sh file]]

Is there anyway to accomplish this goal?

4 Answers 4

29

If you really want to “write exactly bash scripts into Makefiles” then you'll need to do it a bit indirectly. If you just paste the script after the target line, then you'll run into two problems that just cannot be bypassed: the command lines need to be indented with a tab, and dollar signs need to be escaped.

If you use GNU make (as opposed to BSD make, Solaris make, etc.), then you can define your script as a variable using the multi-line definition syntax, and then use the value function to use the raw value of the variable, bypassing expansion.

In addition, as explained by skwllsp, you need to tell make to execute the command list for each target as a single shell script rather than line by line, which you can do in GNU make by defining a .ONESHELL target.

define my_important_task =
# script goes here
endef

my-important-task: ; $(value my_important_task)

.ONESHELL:
5
  • Would you mind taking a look at this file: gist.github.com/ceremcem/a380bb92cbd03c34aba5
    – ceremcem
    Mar 19, 2016 at 0:37
  • 1
    @ceremcem You need .ONESHELL as well. Mar 19, 2016 at 11:34
  • 1
    When using .ONESHELL, probably want to start the recipe with 'set -e'. Without ONESHELL the recipe will abort on first error; but with ONESHELL (and SHELL = /bin/bash) the recipe will not abort on error. 'set -e' goes back to abort on error
    – steve
    Feb 20, 2021 at 22:26
  • This is very useful to be able to have a bash code snippet inside the makefile. How to I access the variables from makefile within the script. Or is there a way to pass a variable to 'my_important_task'
    – Nazar
    Mar 8, 2022 at 19:30
  • You can export a make variable to the environment. Look up export in the GNU make manual. Mar 8, 2022 at 21:02
14

https://www.gnu.org/software/make/manual/html_node/One-Shell.html

If the .ONESHELL special target appears anywhere in the makefile then all recipe lines for each target will be provided to a single invocation of the shell.

You will still need to put a tab character at the beginning of each line, and to double all dollar signs (i.e. replace $ by $$ everywhere in the script).

6
5

If the problem is that your top-level project directory is cluttered with dozens of scripts, then the obvious solution is to create a subdirectory (e.g. called scripts) to put them in.

Run them as ./scripts/scriptname and/or add the scripts directory to your PATH.

If you still want a Makefile to run them with, just create Makefile entries that run ./scripts/scriptname for those targets. e.g.

my-important-task: 
    ./scripts/my-important-task.sh my-parameter

NOTE: If the scripts in ./scripts call other scripts in the same directory, they'll need to either specify the full path to the script, or have the scripts directory in the PATH.

2
  • Maybe that would be an appropriate solution. But writing down in a single file would make things speed up in most cases. We were talking about a proposal yesterday though...
    – ceremcem
    Mar 19, 2016 at 10:49
  • somehow i doubt very much that having make parse a Makefile, search for a target, and then fork a shell to run some shell commands could possibly be faster than just running a shell script. not that the startup speed of make plus a bunch of shell commands or a shell script is at all significant on any modern-ish machine (say, less than 20 years old) unless run thousands of times in a loop.
    – cas
    Mar 19, 2016 at 11:33
-1

BTW ... you can not just use Bash.

In fact you can write make target recipes in any scripting language you want.

Example snippet using Python:

...
SHELL != sh -c "command -v python"
.SHELLFLAGS := -c
.ONESHELL:

# et voilà : the recipe can now be written in pure python :-)
all: foo    
    # use some make variables
    print "# target name is '$@', depends on '$^'"

    import os
    print os.listdir ("..")

foo:
    # use some make variables
    print "# target name is '$@', depends on '$^'"

    import sys
    print 'python version is %s.%s.%s' % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
...

see an example in various languages : https://github.com/lgersman/make-recipes-in-different-scripting-languages-demo

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .