Search code examples
dockersedmakefilesubshell

Makefile multiline command does not work in Docker properly


I am trying to compile HTML from Markdown. My makefile looks like:

MD = pandoc \
    --from markdown --standalone

# ...

$(MD_TARGETS):$(TARGET_DIR)/%.html: $(SOURCE_DIR)/%.md
    mkdir -p $(@D); \
    $(MD) --to html5 $< --output $@; \
    sed -i '' -e '/href="./s/\.md/\.html/g' $@

When I run this on local machine everything works.

When I run the same in Docker I get the following error:

mkdir -p /project/docs; \
pandoc --from markdown --standalone --to html5 /project/source/changelog.md --output /project/docs/changelog.html; \
sed -i '' -e '/href="./s/\.md/\.html/g' /project/docs/changelog.html
sed: can't read : No such file or directory
makefile:85: recipe for target '/project/docs/changelog.html' failed
make: *** [/project/docs/changelog.html] Error 2

Consequent call of make gives the same error but with another file:

sed: can't read : No such file or directory
makefile:85: recipe for target '/project/docs/todo.html' failed

Obviously, make somehow tries sed earlier than HTML is done. But I use multiline syntax ; \ of make so as to avoid using subshell. I also tried && \ but neither of them works. What should I do?


Solution

  • Unfortunately you have been misled by your "obvious" conclusion Obviously, make somehow tries sed earlier than HTML is done :) That's not at all what the problem is. Review your error message more carefully:

    sed: can't read : No such file or directory
    

    Note the can't read :; there's supposed to be a filename there. It should say something like can't read foobar:. So clearly sed is trying to read a file with the name of empty string. Here's the line you're running:

    sed -i '' -e '/href="./s/\.md/\.html/g' /project/docs/changelog.html
    

    The clear culprit here is the empty string argument to -i. The sed man page describes the -i option as:

       -i[SUFFIX], --in-place[=SUFFIX]
    
              edit files in place (makes backup if SUFFIX supplied)
    

    Note how (a) the SUFFIX is optional, and (b) there's no space between the argument and its option. In your example you added a space, which causes sed to believe that the SUFFIX was not provided and to treat the next argument ('') as the filename to be operated on.

    I'm not sure why it worked when you ran it from the command line: I have to assume that your command line version didn't include the empty string or else it didn't include a space between the -i and the ''.

    Use one of:

    sed -i'' -e '/href="./s/\.md/\.html/g' $@
    

    or

    sed --in-place= -e '/href="./s/\.md/\.html/g' $@