Backslash escaping and removal in makefile - makefile

Given this - rather quaint - makefile, which admittedly was specifically designed to clearly illustrate a host of inconsistencies, by trying to find the "rule" how Make handles quoting and backslashes in makefile.
So, the makefile:
prereq = \
\: \
\\: \
\\\: \
\\\\: \
\\\\\: \
\\\\\\:
all: % : $(prereq)
#echo '$+'
%:;
Running, I get:
: \\: \: \\\\: \\: \\\\\\:
Now, let's take a look.
We basically had 6 prerequisites:
\:
\\:
\\\:
\\\\:
\\\\\:
\\\\\\:
Which Make interprets respectively, as:
:
\\:
\:
\\\\:
\\:
\\\\\\:
Do you see a pattern here? Well sure there is.
For any even-number of backslashes, make take all of the backslashes literally, and so \\: remains \\:. \\\\: remains \\\\:. Finally: \\\\\\: remains \\\\\\:. No escaping or any interpretation is done at-all.
As for any odd-number of backslashes, make removes an half of them. To be exact, its an half in the sense of integer-division, where Make removes 1 for 3, 2 for 5. A small twist, for 1 backslash, Make removes 1 backslash. So \: becomes :, \\\: becomes \: and \\\\\: becomes \\:.
Now, sure it must be useful, that make bothers to build all this "theory" for removing backslashes.
But, what is it?

Related

Why 2 backslashes are needed to escape a backslash in vim -c argument?

In Windows 10, I used following command in Git bash 1.9.5,
$ vim -c "%s/^/\=line(".").'. '/g" -c "%s/\//\\/g" -c "%y+" -c "wq" ~/Desktop/sample.txt
in order to,
-c "%s/^/\=line(".").'. '/g": add a sequence of numbers before every line.
-c "%s/\//\\/g": replace all slashes with backslashes.
-c "%y+" -c "wq": copy all text to clipboard, save & exit Vim.
Everything works well except 2, it seems that -c "%s/\//\\/g" argument cannot be dealt correctly by Vim. None of slashes was replaced, and a g was inserted after every first / of each line. For example,
Sample.txt before
A/P/A/T/H
B/P/A/T/H
Sample.txt after
A/gP/A/T/H
B/gP/A/T/H
However, if I execute :%s/\//\\/g in vim, it would works as I expected.
Besides, I've tried these,
-c "%s/\//#/g" can replace all / to # as expected.
-c "%s,/,\\,g" will replace the first / in each line to ,g.
So, I wonder if it is a limit or a known issue of -c argument, or I made mistakes somewhere?
Edit: I just found by chance that -c "%s/\//\\\/g" works as expected.
So could anyone please explain why another \ is needed to escape \?
In Vim, when using regular expressions, the backslash is used to escape characters so it can't be used on its own to represent a literal backslash. For that, you must escape the literal backslash with an escape backslash:
:%s/\//\\/g
So you start with two backslashes no matter what: the escape one and the literal one. That's what Vim expects.
In your shell, backslashes also have a special meaning. When inside double quotes, two consecutive backslashes "collapse" into a single one so, when you think you are telling Vim to do:
:%s/\//\\/g
with:
-c "%s/\//\\/g"
what it actually receives is:
%s/\//\/g
which means: "substitute each slash with an escaped slash (so a simple slash) followed by letter g". Not exactly what you had in mind.
To make sure Vim actually receives the proper command you need to add a third backslash:
-c "%s/\//\\\/g"
Two backslashes are collapsed into a single one and the other one is left intact so you end up with two backslashes, which is what Vim expects:
%s/\//\\/g
Another, better, approach would be to use single quotes, where backslashes are always literal, instead of double quotes:
-c '%s/\//\\/g'
From this page about shell quoting, emphasis mine:
The backslash retains its meaning only when followed by dollar, backtick, double quote, backslash or newline. Within double quotes, the backslashes are removed from the input stream when followed by one of these characters.

Why does "\\\\" equals "\\" in zsh?

So to write a backslash to stdout you do
zsh$ echo \\
\
You would think that to output 2 backslashes, you need to run this:
zsh$ echo \\\\
\
Wrong, You need 6 or 8:
zsh$ echo \\\\\\
\\
zsh$ echo \\\\\\\\
\\
Why on earth would I need 8 backslashes ?
Behaviors in different shells :
ksh$ echo \\\\
\\
zsh$ echo \\\\
\
bash$ echo \\\\
\\
sh$ echo \\\\
\
csh$ echo \\\\
\\
You are probably used to bash's built-in echo, which does not perform expansion of escape sequences. POSIX echo, however, does. zsh follows the POSIX specification, so when you write echo \\\\\\, here's what happens.
Quote removal reduces \\\\\\ to \\\; each escaped \ becomes a literal \.
echo takes the three \ it receives, and replaces the first \\ with \. The remaining \ doesn't precede anything, so it is treated literally.
The final result is an output of two \.
With echo \\\\\\\\, here's what happens.
As before, \\\\\\\\ becomes \\\\, because each pair of backslashes becomes a single \ after quote removal.
echo takes the 4 backslashes, and replaces the first \\ with \ and the second \\ with \ as well, since this time it received two matched pairs.
The final result is again two \.
bash probably inherited its non-POSIX comformance from ksh, sh is by definition a POSIX-comformant shell, and it's best not to think about what csh is doing.
Not only zsh, it's default behavior in terminal, backslash (\) is a special character used to tell bash script that you want to other params/texts in newline, so to avoid newline,u need add another backslash to escape special character, since backslash also used for escape special character

How to write fancy-indented multi-line brace expansion in Bash?

I'm dealing with a line such :
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR1","$DEST_DIR2", ..., "$DEST_DIRN"}
This line is quite long. I want to cut it so its width will fit into a 80 columns line. I tried to escape an end of line with a backslash, but space alignement breaks the expansion :
$ echo "ha"{a,b,\
> c}
ha{a,b, c}
You could use this disgusting hack.
echo "ha"{a,b,\
> ` `c}
It opens a subshell with nothing in it, but gets processed before the expansion so the expansion just sees an empty space
This is the normal behaviour. From the Bash reference manual:
3.5.1 Brace expansion
Brace expansion is a mechanism by which arbitrary strings may be
generated. This mechanism is similar to filename expansion (see
Filename Expansion), but the filenames generated need not exist.
Patterns to be brace expanded take the form of an optional preamble, followed by either a series of comma-separated strings or a sequence
expression between a pair of braces, followed by an optional
postscript. The preamble is prefixed to each string contained within
the braces, and the postscript is then appended to each resulting
string, expanding left to right.
Brace expansion does not allow spaces in between elements that get placed between \ and the next element in the following line.
And why? Because it gets removed when being processed:
3.1.2.1 Escape Character
A non-quoted backslash ‘\’ is the Bash escape character. It preserves
the literal value of the next character that follows, with the
exception of newline. If a \newline pair appears, and the backslash
itself is not quoted, the \newline is treated as a line continuation
(that is, it is removed from the input stream and effectively
ignored).
So when you say
something + \ + <new line> + another_thing
Bash converts it into
something + another_thing
What can you do, then?
Add a backslash and then start writing from the very beginning on the next line:
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR1",\
"$DEST_DIR2",\
...,\
"$DEST_DIRN"}
Some examples
When you say:
$ echo "ha"{a,b\
> c}
ha{a,b c}
And then move the arrow up you'll see this is the command that was performed:
$ echo "ha"{a,b c}
So just say:
$ echo "ha"{a,b\
> c}
haa habc
And you'll see this when moving up:
$ echo "ha"{a,b,c}
Another example:
$ cat touch_files.sh
touch X{1,\
2,3}
$ bash touch_files.sh
$ ls X*
X1 X2 X3
Thus I accepted #123's answer, here's the one I choosed :
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR1","$DEST_DIR2"}
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR3","$DEST_DIR4"}
There are not a lot of destination directories here, so I think it's a good balance between the fancy-and-disgusting hack and the frustrating backslash which breaks the indentation.
I would do it is follows (though it only addresses your particular task of creating multiple directories and doesn't answer the question as stated in the title):
for d in \
"$DEST_DIR1" \
"$DEST_DIR2" \
... \
"$DEST_DIRn" \
;
do
mkdir -p "$DEST_ROOT_PATH/$d"
done
The advantage of this approach is that maintaining the list is a little easier.
In general, you should stop sticking to syntactic sugar when you notice that it starts causing inconveniences.

Makefile :*** unterminated call to function `foreach': missing `)'. Makefile

I am getting above error when i execute my target. I have terminated the foreach with a closing brace but still it persists.
$(addprefix c_lr, $(RLRG_LRGS)):c_lr%:
$(foreach i, $(days), \
if [ "$(day)" = "$(i)" ] ; then \
$(ECHO) This is a valid . ; \
fi ; \
)
On the first recipe line, there is a space after the backslash:
$(foreach i, $(days), \
^---space here
A backslash only escapes a newline if the newline comes directly after the backslash. If there's a space after the backslash, then it's not an escaped newline, it's just a backslash followed by a space.
I highly recommend using an editor (such as GNU Emacs) that has a good Makefile editing mode with the ability to highlight questionable whitespace. This makes it easy to find mistakes like that.

How do I properly escape data for a Makefile?

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)
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.
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.
I don't see how that makefile can work as you say. A pattern rule cannot be the default.
You're missing a `$` in `$(wildcard ...)`, so I think you haven't posted what you're really testing.
You should escape newlines too.

Resources