printing a variable in a makefile with multiple spaces - makefile

Consider the following simple makefile:
define HELP_MSG
Usage:\n
make help -show this message\n
make help -show spaces and then this message\n
endef
export HELP_MSG
help:
#echo $$HELP_MSG
which outputs:
Usage:
make help -show this message
make help -show spaces and then this message
How can I have #echo respect the extra spacing on the second output line?

There is no portable way to print formatted text with echo. The -e option to echo is not standardized and not all versions of echo support -e. You should never try to print anything other than simple text that you know does not begin with a dash (-). Basically, anything other than a simple static string.
For anything more complex, you should use printf.
Also, if you want to print non-trivial text you MUST quote it, otherwise the shell will interpret it and mess it up for you.
help:
#printf '%s\n' "$$HELP_MSG"

You can use -e along with echo and use tab for space. Eg:
define HELP_MSG
Usage:\n
make help -show this message\n
make help \t-show spaces and then this message\n
endef
export HELP_MSG
help:
#echo -e $$HELP_MSG
To add custom spaces, use printf or " " in echo. Eg:
define HELP_MSG
Usage:\n
make help -show this message\n
make help -show spaces and then this message\n
endef
export HELP_MSG
help:
#echo -e "$$HELP_MSG"
#printf '%s\n' "$$HELP_MSG"

Related

Declare variable in makefile using the bash for

I want to declare a variable using a for from bash in a makefile. That's what I tried:
SRCS="path/to/foo.c path/to/boo.c path/to/bar.c"
OBJS=$(for file in $SRCS; do TEMP="$TEMP $(basename $file .c).o"; done; echo $TEMP)
This command:
for file in $SRCS; do TEMP="$TEMP $(basename $file .c).o"; done
works on bash when echoing TEMP. But OBJS in makefile is empty. What am I missing here?
Desired output:
foo.o boo.o bar.o
First of all there's better ways to do this without using bash's for. You could use the make constructs to generate these lists. See the documentation on $(notdir), $(addprefix...) and $(addsuffix ...)
However, to answer your question on this particular example:
First, you are putting quotes around sources. Make does not interpret quotes as special characters, and thus SRCS will expand to "path/to/foo.c path/to/boo.c path/to/bar.c" (including the quotes). This will mess up your for loop later on.
The next thing is the reference to $SRCS -- make will interpret this as $S followed by the literal RCS (which is not what you want). You have to use braces around multi0letter variables in bash as so -- $(SRCS)
Next, TEMP=$TEMP.... When make sees $TEMP, it will immediately attempt to expand it. Because TEMP is not set to anything in the make context, it will expand to a null string -- this is before it invokes the bash shell... What you wanted to do in this case is use $$TEMP, which make will expand to $TEMP.
The following does what you want: note the $(info) lines are just for debugging.
SRCS:=path/to/foo.c path/to/boo.c path/to/bar.c
OBJS:=$(shell for file in $(SRCS); do TEMP="$$TEMP $$(basename $$file .c).o"; done; echo $$TEMP)
$(info for file in $(SRCS); do TEMP="$$TEMP $$(basename $$file .c).o"; done; echo $$TEMP)
$(info OBJS=$(OBJS))
If you wanted to do this in makefile without bash or sh, you might try:
OBJS:=$(notdir $(SRCS:.c=.o))
Note: this is gnu-make specific syntax and may not work on other makes.

Echo without interpreting newline

I have a makefile where one of the actions requires that I write a string \noslide to a textfile called modeset.txt, so I'm using 'echo'. Although the -E flag should disable interpretation of backslash escapes echo processes the flag as part of the sting (see output below).
Here is a stripped down snippet from my makefile:
.PHONY: target all
all: target
target:
echo -E "\noslide" > modeset.txt
clean:
rm -f modeset.txt
The content of the modeset.txt file shows that echo gobbled the -E as part of the string.
-E
oslide
In a shell however, echo -e and echo -E work as expected, so what is the makefile environment doing to cause this?
OS: Vanilla Debian 8 from official sources only.
echo is not portable, beyond simple text. Many different versions exist that behave differently, and even within the same system it can depend on what shell you use (i.e., the shell's builtin echo vs the system's /bin/echo).
If you want to display anything other than simple text followed by a newline, you should use printf not echo:
target:
printf '\\noslide\n' > modeset.txt

echo special characters to a file in bash

I am trying to make a script to help me back port source code to debian files.
trying to write debian/rules file
~$ echo -e "#!/usr/bin/make -f\n%:\n\tdh \$#" > debian/rules
bash: !/usr/bin/make: event not found
i think the # and ! are screwing this up. How can I echo these components of the string without having the shell try to execute them?
I have tried using "#" and "!" but then i would get the exactly that: "!"
I want a rules file that looks exactly like this:
#!/usr/bin/make -f
%:
dh $#
what should I do?
! is not escaped by double quotes
'#!/usr/bin/make -f\n%:\n\tdh \'"$#"
should be fine. However, please look at here-documents:
cat <<MAKEFILE
#!/usr/bin/make -f
%:
dh $#
MAKEFILE
I have an alternative method, using ANSI hex codes (or even Unicode hex codes, if you're clever).
$ echo -e "\x23\x21/usr/bin/make -f\n\x25:\n\tdh \x24\x40"
#!/usr/bin/make -f
%:
dh $#
My method makes a bit more intuitive sense to me, though the use of here documents by #sehe is ingenious. (It would also require less look-ups if you're not very familiar with ANSI.) I needed to know that # is U+0023; that ! is U+0021; etc. I work with Unicode codepoints all the time, so it's quick.

How to split in GNU makefile list of files into separate lines

I have a variable containing list of files separated with string _NEWLINE_. I need to output that variable into a file so that each file is in a separate line. The trick is that it needs to works on FreeBSD and Solaris.
This is what I am trying now:
echo "lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf" | sed 's|_NEWLINE_|\'$'\n|g'
This works on FreeBSD and in shell on Solaris. But when run in GNUmakefile on Solaris I am getting this (notice $ at the end of each line):
lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf$
lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src$
lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf$
If I remove \'$' from sed then it works on Solaris but doesn't on FreeBSD. Maybe there is a way of telling which version to use depending on which system the makefile is executed?
EDIT:
Thanks to the solution proposed by bobbogo I created an exemplary makefile that provides the desired outcome and seems to be working on both FreeBSD and Solaris:
one-line := lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/\
priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/comm\
unity.conf: lib/alarms/priv/snmp_conf/community.conf
many-lines := { echo '$(subst _NEWLINE_,' && echo ',${one-line})'; }
.PHONY: all
all:
$(shell $(many-lines) > test.txt)
If this is GNU make, then do it all in make.
one-line := lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf
define \n
endef
many-lines := $(subst _NEWLINE_,${\n},${one-line})
Now ${many-lines} has just what you want. Annoyingly, it's quite hard to use in shell lines. If you do this:
tgt:
echo '${many-lines}'
make will invoke a separate shell for each line. The first shell invocation will get an un-paired ' and exit with an error.
.ONESHELL:
tgt:
echo '${many-lines}'
will work in an invasive sort of way. The proper fix is to ensure each line of ${many-lines} has valid sh syntax. Some mouthfull like:
echolines = $(subst ${\n},'${\n}echo ',echo '${many-lines}')
.PHONY: aa
aa:
$(call echolines,${many-lines})
Sheesh.
Tried many different solutions, including defining \n as mentioned in this answer: Add a newline in Makefile 'foreach' loop
The real problem is inconsistent implementation of the echo command across platforms, and the fact that by default make invokes shell commands using sh, which itself is quite inflexible.
I found a better way thanks to this answer: "echo -e" when called directly and when called via a shell-script
The better way is to use printf instead of echo
Construct the string with \n instead of _NEWLINE_ to separate parts that go into separate lines in the output file:
some_string = lib/alarms-1.2/priv/snmp_conf/target_params.conf: lib/alarms/priv/snmp_conf/target_params.conf\nlib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf\n
and then in the makefile print it simply as this:
#printf "$(some_string)" >> $(some_file)
Works on both, FreeBSD and Solaris.
Disclaimer: I have no experience with Solaris or FreeBSD... here goes anyway.
In make, you can use $(patsubst pattern,replacement,text) to substitute a pattern.
try this...
FILENAMES := "lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf"
.PHONY: all
all:
#echo $(patsubst _NEWLINE_,${\n},$(FILENAMES))
As an alternative, I think your first approach will work, if you just double the $ to "escape" it:
sed 's|_NEWLINE_|\'$$'\n|g'

Multi-line bash commands in makefile

Considering that every command is run in its own shell, what is the best way to run a multi-line bash command in a makefile? For example, like this:
for i in `find`
do
all="$all $i"
done
gcc $all
You can use backslash for line continuation. However note that the shell receives the whole command concatenated into a single line, so you also need to terminate some of the lines with a semicolon:
foo:
for i in `find`; \
do \
all="$$all $$i"; \
done; \
gcc $$all
But if you just want to take the whole list returned by the find invocation and pass it to gcc, you actually don't necessarily need a multiline command:
foo:
gcc `find`
Or, using a more shell-conventional $(command) approach (notice the $ escaping though):
foo:
gcc $$(find)
As indicated in the question, every sub-command is run in its own shell. This makes writing non-trivial shell scripts a little bit messy -- but it is possible! The solution is to consolidate your script into what make will consider a single sub-command (a single line).
Tips for writing shell scripts within makefiles:
Escape the script's use of $ by replacing with $$
Convert the script to work as a single line by inserting ; between commands
If you want to write the script on multiple lines, escape end-of-line with \
Optionally start with set -e to match make's provision to abort on sub-command failure
This is totally optional, but you could bracket the script with () or {} to emphasize the cohesiveness of a multiple line sequence -- that this is not a typical makefile command sequence
Here's an example inspired by the OP:
mytarget:
{ \
set -e ;\
msg="header:" ;\
for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
msg="$$msg :footer" ;\
echo msg=$$msg ;\
}
The ONESHELL directive allows to write multiple line recipes to be executed in the same shell invocation.
all: foo
SOURCE_FILES = $(shell find . -name '*.c')
.ONESHELL:
foo: ${SOURCE_FILES}
FILES=()
for F in $^; do
FILES+=($${F})
done
gcc "$${FILES[#]}" -o $#
There is a drawback though : special prefix characters (‘#’, ‘-’, and ‘+’) are interpreted differently.
https://www.gnu.org/software/make/manual/html_node/One-Shell.html
Of course, the proper way to write a Makefile is to actually document which targets depend on which sources. In the trivial case, the proposed solution will make foo depend on itself, but of course, make is smart enough to drop a circular dependency. But if you add a temporary file to your directory, it will "magically" become part of the dependency chain. Better to create an explicit list of dependencies once and for all, perhaps via a script.
GNU make knows how to run gcc to produce an executable out of a set of .c and .h files, so maybe all you really need amounts to
foo: $(wildcard *.h) $(wildcard *.c)
What's wrong with just invoking the commands?
foo:
echo line1
echo line2
....
And for your second question, you need to escape the $ by using $$ instead, i.e. bash -c '... echo $$a ...'.
EDIT: Your example could be rewritten to a single line script like this:
gcc $(for i in `find`; do echo $i; done)

Resources