2

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.

Community
  • 1
  • 1
Violapterin
  • 337
  • 2
  • 14

2 Answers2

1

In the *nix shell, there are three types of quotation marks used: single ('), double ("), and back quotes (`). Only single and double quotation marks are relevant here. If quotation marks are used as quotation marks (as opposed to parts of file names, etc.) they must always be used in pairs. If they are not used as quotation marks but as parts of filenames, etc., then they must be escaped or must themselves be quoted or the shell will try to pair them and interpret them as quotation marks. Single quotes do not allow the shell to modify anything within them, for example wildcards cannot be expanded and shell variables cannot be substituted. Double quotes can protect single quotes, and do allow the shell to make some substitutions, for example, variable substitution. Variable names can be enclosed in curly braces to make it unambiguous where their names begin and end. For example, if you have defined a variable "FILENAME", you can write: $ ls -l "${FILENAME}"foo.c The shell can get inside the double quotation marks and replace FILENAME by whatever it is supposed to refer to, and it also knows that the variable name is FILENAME rather than FILENAMEfoo.c. I don't know anything about Make, so I can't tell you what to do, but I can tell you that in the shell $@ has a special meaning similar to $*. However, if you enclose it in double quotes, that is "$@", it will be replaced by "$1" "$2" etc., that is, by the first, second, and so on, arguments ENCLOSED IN DOUBLE QUOTES. This should protect single quotes. So, I would suggest that you try replacing $@ by "$@" in your Make file and try running it. See what happens. Good luck.

Thomas Hedden
  • 403
  • 3
  • 9
  • `$@` is interpreted by make, not sent to the shell; to send a literal `$@` sequence to the shell, a makefile would need to contain `$$@` -- but since the desired argument to `-o` isn't in fact the shell's argument list, that's not appropriate here. – Charles Duffy Jan 10 '17 at 17:05
  • See http://stackoverflow.com/questions/23330243/gnu-makefile-how-to-and-when-to-quote-strings – Charles Duffy Jan 10 '17 at 17:09
  • Thank you @Tom Hedden, as you reminded me to add quotes, and I upvoted you. But I decided to accept myself, since my code excerpt is more complete, and so a better reference to future reader. – Violapterin Jan 16 '17 at 09:51
0

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 "$<"
Violapterin
  • 337
  • 2
  • 14