7

Note: This is not a duplicate of Is it possible to create a multi-line string variable in a Makefile and other such questions. This question requests POSIX compliant solutions that do not depend Bash-only or GNU Make-only features. The other question on Stack Overflow does not have this requirement.

Take this shell script using the BSD sed on macOS High Sierra:

printf 'foo\nbaz\n' | sed '/foo/a\
bar
'

The output is:

foo
bar
baz

How can I put the same thing in a Makefile? Here is what I tried but it does not seem to work:

all:
    printf 'foo\nbaz\n' | sed '/foo/a\\
    bar\
    '

This leads to errors:

$ make
printf 'foo\nbaz\n' | sed '/foo/a\\
/bin/sh: -c: line 0: unexpected EOF while looking for matching `''
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [all] Error 2

How can I write the above sed invocation correctly in a Makefile such that when make executes the all target it feeds the trailing slash and the newlines correctly to the shell?

Note: I would like the Makefile to be POSIX compliant and use only features from http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html.

Lone Learner
  • 18,088
  • 20
  • 102
  • 200
  • 2
    @tripleee This is not a duplicate of the question you have linked to. If you read this question, it specifically says I want a POSIX compliant solution. The solution in the question you have linked to uses `define` which is not defined in POSIX make. – Lone Learner Mar 11 '19 at 05:51
  • Have you tried multiple `-e` ? You can often get away with things like this `printf 'foo\nbaz\n' | sed -e '/foo/{a\' -e 'bar' -e 'a\' -e 'bar2' -e'}'` ... unsure if helpful with your sed. Each `-e` implies a newline of sorts though it's finicky with append. – stevesliva Mar 22 '19 at 18:37
  • @stevesliva Using `-e` to imply newline is not POSIX. It did not work with BSD sed on mac. The POSIX requires `-e` argument to be complete editing command. So when I try your command on Mac, I get error: `": undefined label 'ar'`. BSD sed thinks `-e 'bar'` as the `b` (branch) command with `ar` label. It didn't work. Each `-e` implying a newline of sorts is very much GNU behavior. it is not POSIX behavior. Add `--posix` argument to your sed command, then it will not work on Linux too. – Lone Learner Mar 25 '19 at 16:51

2 Answers2

6

Parameter Expansion with Suffix Removal in sed

Here is a solution that puts <newline>x (i.e., newline followed by the character x) in a variable and then uses this variable in the sed command. While using this variable, we use shell parameter expansion to get rid of the x suffix, so that we are only left with a newline.

all:
    NX=$$(printf '\nx'); printf 'foo\nbaz\n' | sed "/foo/a\\$${NX%x}bar$${NX%x}"

The value of NX is <newline>x, so ${NX%x} evaluates to just <newline>. Here is the output:

$ make
NX=$(printf '\nx'); printf 'foo\nbaz\n' | sed "/foo/a\\${NX%x}bar${NX%x}"
foo
bar
baz

Simplifying Parameter Expansion in sed with make Macros

We can simplify the usage of the previous solution with make macros. We put all the unwieldy parts into macros. Then we expand those macros in the shell commands. Here is an example:

NX = NX=$$(printf '\nx')
NL = $${NX%x}

all:
    $(NX); printf 'foo\nbaz\n' | sed "/foo/a\\$(NL)bar$(NL)"

Here is the output:

$ make
NX=$(printf '\nx'); printf 'foo\nbaz\n' | sed "/foo/a\\${NX%x}bar${NX%x}"
foo
bar
baz

Parameter Expansion with Suffix Removal Before sed

Here is another possible solution on similar lines. Instead of writing the unwiedly syntax of ${NX%x} within the sed command twice, it is simplified by assigning ${NX%x} itself to another shell variable named NL and ${NL} is used instead in the sed command.

all:
    NX=$$(printf '\nx'); NL=$${NX%x}; printf 'foo\nbaz\n' | sed "/foo/a\\$${NL}bar$${NL}"

Here is the output:

$ make
NX=$(printf '\nx'); NL=${NX%x}; printf 'foo\nbaz\n' | sed "/foo/a\\${NL}bar${NL}"
foo
bar
baz

Simplifying Parameter Expansion Before sed with make Macros

The usage of the above solution can be simplified further with make macros as follows:

NX = NX=$$(printf '\nx'); NL=$${NX%x}
NL = $${NL}

all:
     $(NX); printf 'foo\nbaz\n' | sed "/foo/a\\$(NL)bar$(NL)"

Here is the output:

$ make
NX=$(printf '\nx'); NL=${NX%x}; printf 'foo\nbaz\n' | sed "/foo/a\\${NL}bar${NL}"
foo
bar
baz
Susam Pal
  • 32,765
  • 12
  • 81
  • 103
-1

You can't except by writing your recipe in a separate shell script and calling that instead of open-coding it in a make recipe.

MadScientist
  • 92,819
  • 9
  • 109
  • 136