Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-line bash commands in makefile

Tags:

bash

makefile

People also ask

Can you write bash in makefile?

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.

How do you comment multiple lines in makefile?

' # ' in a line of a makefile starts a comment. It and the rest of the line are ignored, except that a trailing backslash not escaped by another backslash will continue the comment across multiple lines.

How do I make multiple lines in terminal?

Using a Backslash. The backslash (\) is an escape character that instructs the shell not to interpret the next character. If the next character is a newline, the shell will read the statement as not having reached its end. This allows a statement to span multiple lines.

What is $$ in makefile?

$$ means be interpreted as a $ by the shell. the $(UNZIP_PATH) gets expanded by make before being interpreted by the shell.


You can use backslash for line continuation. However note that the shell receives the whole command concatenated into a single line, so you also need to terminate some of the lines with a semicolon:

foo:
    for i in `find`;     \
    do                   \
        all="$$all $$i"; \
    done;                \
    gcc $$all

But if you just want to take the whole list returned by the find invocation and pass it to gcc, you actually don't necessarily need a multiline command:

foo:
    gcc `find`

Or, using a more shell-conventional $(command) approach (notice the $ escaping though):

foo:
    gcc $$(find)

As indicated in the question, every sub-command is run in its own shell. This makes writing non-trivial shell scripts a little bit messy -- but it is possible! The solution is to consolidate your script into what make will consider a single sub-command (a single line).

Tips for writing shell scripts within makefiles:

  1. Escape the script's use of $ by replacing with $$
  2. Convert the script to work as a single line by inserting ; between commands
  3. If you want to write the script on multiple lines, escape end-of-line with \
  4. Optionally start with set -e to match make's provision to abort on sub-command failure
  5. This is totally optional, but you could bracket the script with () or {} to emphasize the cohesiveness of a multiple line sequence -- that this is not a typical makefile command sequence

Here's an example inspired by the OP:

mytarget:
    { \
    set -e ;\
    msg="header:" ;\
    for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
    msg="$$msg :footer" ;\
    echo msg=$$msg ;\
    }

The ONESHELL directive allows to write multiple line recipes to be executed in the same shell invocation.

all: foo

SOURCE_FILES = $(shell find . -name '*.c')

.ONESHELL:
foo: ${SOURCE_FILES}
    FILES=()
    for F in $^; do
        FILES+=($${F})
    done
    gcc "$${FILES[@]}" -o $@

There is a drawback though : special prefix characters (‘@’, ‘-’, and ‘+’) are interpreted differently.

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


Of course, the proper way to write a Makefile is to actually document which targets depend on which sources. In the trivial case, the proposed solution will make foo depend on itself, but of course, make is smart enough to drop a circular dependency. But if you add a temporary file to your directory, it will "magically" become part of the dependency chain. Better to create an explicit list of dependencies once and for all, perhaps via a script.

GNU make knows how to run gcc to produce an executable out of a set of .c and .h files, so maybe all you really need amounts to

foo: $(wildcard *.h) $(wildcard *.c)

What's wrong with just invoking the commands?

foo:
       echo line1
       echo line2
       ....

And for your second question, you need to escape the $ by using $$ instead, i.e. bash -c '... echo $$a ...'.

EDIT: Your example could be rewritten to a single line script like this:

gcc $(for i in `find`; do echo $i; done)