55

I'm dynamically generating config.mk with a bash script which will be used by a Makefile. The file is constructed with:

cat > config.mk <<CFG
SOMEVAR := $value_from_bash1
ANOTHER := $value_from_bash2
CFG

How do I ensure that the generated file really contains the contents of $value_from_bash*, and not something expanded / interpreted? I probably need to escape $ to $$ and \ to \\, but are there other characters that needs to be escaped? Perhaps there is a special literal assignment I've not heard of?

Spaces seems to be troublesome too:

$ ls -1
a b
a
$ cat Makefile
f := a b
default_target:
    echo "$(firstword $(wildcard ${f}))"
$ make
a

If I use f := a\ b it works (using quotes like f := 'a b' did not work either, makefile just treats it as a regular character)

smci
  • 32,567
  • 20
  • 113
  • 146
Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
  • What version of Make are you using? – Beta Oct 05 '11 at 01:33
  • 1
    This is not a Makefile question but a bash one. I think you know how you want your makefile look like , so your problem is escaping things in your bash here-document. Have a look at this http://tldp.org/LDP/abs/html/here-docs.html – mb14 Oct 05 '11 at 12:00
  • 1
    No, I'm not asking how to replace strings in bash, but what to do for getting strings from bash in a Makefile without modifying the meaning (e.g. if `echo "$var"` shows `"whatever \'`, the same should be displayed in the Makefile) – Lekensteyn Oct 05 '11 at 12:41

3 Answers3

84

Okay, it turned out that Makefiles need little escaping for itself, but the commands which are executed by the shell interpreter need to be escaped.

Characters which have a special meaning in Makefile and that need to be escaped are:

  • sharp (#, comment) becomes \#
  • dollar ($, begin of variable) becomes $$

Newlines cannot be inserted in a variable, but to avoid breaking the rest of the Makefile, prepend it with a backslash so the line break will be ignored.

Too bad a backslash itself cannot be escaped (\\ will still be \\ and not \ as you might expect). This makes it not possible to put a literal slash on the end of a string as it will either eat the newline or the hash of a following comment. A space can be put on the end of the line, but that'll also be put in the variable itself.

The recipe itself is interpreted as a shell command, without any fancy escaping, so you've to escape data yourself, just imagine that you're writing a shellscript and inserting the variables from other files. The strategy here would be putting the variables between single quotes and escape only ' with '\'' (close the string, insert a literal ' and start a new string). Example: mornin' all becomes 'morning'\'' all' which is equivalent to "morning' all".

The firstword+wildcard issue is caused by the fact that filenames with spaces in them are treated as separate filenames by firstword. Furthermore, wildcard expands escapes using \ so x\ y is matches as one word, x y and not two words.

nonchip
  • 1,084
  • 1
  • 18
  • 36
Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
10

It seems that the full answer to this question is found nowhere on the internet, so I finally sat down and figured it out for the Windows case.

Specifically, the "Windows case" refers to file names that are valid in Windows, meaning that they do not contain the characters \, /, *, ?, ", ^, <, >, |, or line breaks. It also means \ and / are both considered valid directory separators for the purposes of Make.

An example will clear it up better than I can explain. Basically, if you are trying to match this file path:

Child\a$b  {'}(a.o#$@,&+=~`),[].c

Then you have to write these rules:

all: Child\\a$$b\\\ \\\ {'}(a.o\#$$@,&+=~`),[].o

%.o: %.c
    $(CC) '$(subst ','"'"',$(subst \,,$(subst \\,/,$+)))'

Stare at it for a long time and it'll sort of start making some remote sense.

This works in my MSYS2 environment, so I presume it is correct.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • That substitution pattern looks odd, is that syntactically valid? It seems to replace `'` by `'"'"'` (after replacing `\` by `/`), is that correct? Oh, basically you are replacing `'` by `'`, then a quoted literal `"'"`, and then continue the single quotes which start before and after the argument. Do you have some references for this escaping? – Lekensteyn Nov 25 '16 at 07:17
  • @Lekensteyn: Yup, [http://stackoverflow.com/a/1250279/541686](here). However, that's by far the easiest aspect of this problem. The real difficulty is not messing up spaces and other characters, and I think my answer still fails in some cases... I'll update it when I have a better one. – user541686 Nov 25 '16 at 07:33
  • Ah yes, that is sh-style escaping, not necessarily for Windows commands. IIRC `^` is the escape character used in Windows (batch scripts). – Lekensteyn Nov 25 '16 at 08:08
  • @Lekensteyn: Actually this isn't for batch scripts, it's for sh-style escapes... but on Windows (MSYS2) rather than Linux. Sorry for the confusion, the main reason I posted the answer was that I was looking for it myself, not so much that I thought it'd help your case necessarily. – user541686 Nov 25 '16 at 08:31
  • Yeah it works, but the prerequisite is considered not exist and redundant compile will take place. – Tuff Contender Jan 05 '20 at 11:50
2
  1. I don't see how that makefile can work as you say. A pattern rule cannot be the default.
  2. You're missing a `$` in `$(wildcard ...)`, so I think you haven't posted what you're really testing.
  3. You should escape newlines too.
Beta
  • 96,650
  • 16
  • 149
  • 150
  • Thanks for your answer, I've indeed made a typo when making the example. Is it enough to escape just newlines, spaces, dollars and backslashes? – Lekensteyn Oct 05 '11 at 12:39
  • @Lekensteyn, I think so. But you might want to rethink your design; Make was never really intended for hard-core string manipulation. – Beta Oct 05 '11 at 12:58
  • The variables are in fact file paths. Usually, these do not contain special characters like newlines (which I'll therefore ignore), `$` (but you'll never know for sure). Possible special characters which need to be taken care of are `"`, `'` and spaces. – Lekensteyn Oct 05 '11 at 13:12
  • could you improve the answer by summarizing the list of characters to be escaped? – Lekensteyn Oct 18 '11 at 17:38