Search code examples
bashreplacemakefileescapingspecial-characters

Tell Makefile to send `\`-escaped filenames to Bash


Background: I have some 400 short articles that are, throughout the past few years, written by me in Chinese, and saved in plain text in my MacBook. In managing these files, I have consistently used $, &, !, and # in naming, giving them each a personal meanings. Apart from that, it is also natural to include (, ), ' (apostrophe), and (whitespace) in filenames.

These are all Bash-reserved characters (called BRCs from now on). There is a table of all BRCs. Those I listed above are all BRCs that I used, though. I didn't suppose BRCs are problematic in the before, because I have read that the only characters problematic in filenames are : and /. Besides, in my daily experience, not only did Mac always allow them in filenames, but they may be escaped in Bash as well, if command-line input is necessary.

But now, I am resolved to write a script to cat these plain text files of my writings, to combine them to a XeLaTeX template I have prepared, and to compile them into PDFs. I am just implementing the first step of my script, and I am stuck. Sigh.

Indeed, I use Make to list, by wildcard *, filenames in question, and in doing so Makefile feeds to Bash un-escaped strings with BRCs. Thus Bash interprets BRCs' special meanings, rather than taking literally. It occurs to me BRCs need to be escaped.

I know it is possible to escape them automatically in Bash, but I have difficulty in the first place to feed them, being literal, into Bash. And I know Make is capable to manipulate strings, but I encountered numerous difficulties —— see below. Or even it can be done purely by virtue of Make, it does not look easy to cover all cases, nor does it seem very readable. Is there really not a built-in way to feed a string with BRC into Bash?


Example: To make it more relevant and repeatable to the readers, consider this contrived situation.

I made a directory containing these files:

main.cpp  foo's.h  foo's.cpp  makefile

With the content of makefile:

SRCS = $(wildcard *.cpp)

OBJS = $(SRCS:.cpp=.o)

MAIN = test

all : $(MAIN)

$(MAIN) : $(OBJS)
    g++ -o $@ $^

./%.o : ./%.cpp
    g++ -o $@ -c $<

Makefile emits several errors, most of them are, or are variants of,

clang: error: no such file or directory: '.cpp'

Though Make does not state directly, I suppose the culprit is the BRC ' within foo's.cpp and foo's.h. When I retrieve their values directly in a recipe of Make, they need be escaped in Bash.

As I understand it, Makefile's automatic variables are all prefixed with $, and thus need not be escaped if appearing by themselves. I also know that $$ gives literal $, as stated here. Thus I tried, then,

SRCS = $(wildcard *.cpp)
HOLD = $(SRCS:.cpp=.o)
OBJS = $(HOLD:'=\')

This does not work.

The same can be said of other BRCs, if you substitute any of them for ' and repeat the above, except recall as I said that literal $ is given by $$. Furthermore, I don't know how to deal with (whitespace), since Makefile is seemingly designed to parse space-separated list.

You may advise me, "Please just don't include BRCs in a filename, to stay from trouble...." But not only is it time-consuming to rename every BRC away one by one, but I too would think it unfortunate if I could not continue using BRCs according to the convention I invented. Thus, I want to know whether Make really cannot handle BRCs in a satisfying way.


Solution

  • Oh, just use quote around variables....

    In the case of a list, we have to quote every one, so use both addsuffix and addprefix. Since normally Make disallow assignment in a rule, use eval to get around this. (ESC stands for "escape")

    SHELL := /usr/bin/env bash
    SRCS = $(wildcard *.cpp)
    OBJS = $(SRCS:.cpp=.o)
    MAIN = test
    ESC =
    
    all : $(MAIN)
    
    $(MAIN) : $(OBJS)
        $(eval ESC := $^)
        $(eval ESC := $(addprefix ",$(ESC)))
        $(eval ESC := $(addsuffix ",$(ESC)))
        g++ -o "$@" $(ESC)
    
    %.o : %.cpp
        g++ -o "$@" -c "$<"