Reputation: 7423
I have a makefile like this:
file1 = "path/to/some/file"
header="col1;col2;col3"
$(file1):
some steps to create the file
call_perl_script:$(file1)
${perl} script.pl in=header
The header is currently hardcoded, and also contained in the generated file1
. I need to fetch the header from file1
. Somehow I have changed it like
file1 = "path/to/some/file"
$(file1):
some steps to create the file
$(eval header="$(shell $(sed) -n "/^col1;col2;col3/p" $(file1))")
call_perl_script: $(file1)
${perl} script.pl in=$(header)
It works fine but want to know if it is correct way to work with target specific variable. The header does not get its value passed until used with eval
.
Also if I print $(header)
in the call_perl_script
target, it prints correctly but if I use an if
condition to check if the variable is empty and set a default value, then it does not work. It sets the value of header
in the if
block irrespective of the value from the sed
output.
call_perl_script: $(file1)
${echo} $(header)
ifeq "$(header)" ""
$(eval header="col1;col2;col3")
endif
${perl} script.pl in=$(header)
Upvotes: 1
Views: 244
Reputation: 3326
It's unclear from the original question, but I presume that file1
is part of the useful output of the Makefile, and needs to be kept around after the Perl script runs.
Regardless, it seems fine to use a command substitution here, which saves you from having to create extra targets or temp files, and clean those up afterwards.
file1 = path/to/some/file
$(file1):
some steps to create the file
call_perl_script: $(file1)
perl script.pl in="`sed -n '/^col1;col2;col3/p' $<`"
The double quotes around the backticks are absolutely required here to prevent the shell from treating the ;
s as command separators, which would otherwise result in an error. You could use GNU Make's $(shell …)
function here instead of backticks, but it doesn't save any typing, and isn't portable to non-GNU systems, if that's a concern.
Assuming the header is the first line of the file, you can simplify that last target further, to just:
call_perl_script: $(file1)
perl script.pl in="`head -n 1 $<`"
The head
command is specified by POSIX, so it's basically guaranteed to be available. You could also use sed -n 1p
.
If you're unfamiliar with command substitutions (the commands in `backticks` here), they run a command and return its output, with trailing newlines removed. In Bash and other modern shells, you usually see this as $(command with args)
. However, by default, Makefile recipes run with /bin/sh
, the POSIX/Bourne shell—not your user's login shell—unless otherwise specified.
The $<
above refers to the rule's first dependency, $(file1)
; see "Automatic Variables" in the manual, if this was unfamiliar to you.
I think the OP's question originates from the somewhat-confusing concept of variables' "scope" in Makefiles. There are really two separate ones:
You can send Make variables in to the scope of a recipe with
$(VARNAME)
or ${VARNAME}
variable syntaxexport VARNAME
outside the recipe, then $$VARNAME
using shell variable syntax inside the recipe…but you can't go the other way. This means it's just not possible to pass values between Makefile targets with variables alone.
If you're thinking of reaching for $(eval …)
, now you have two problems. There is almost definitely a simpler solution for what you're trying to do.
For clarity, I recommend omitting quotes from Makefile variable definitions for simple string values like header
in the OP's first example. Make itself doesn't care about those quotes, but the shell does.
Instead, wrap the Make variables in quotes when referenced within your recipes, where the shell would require them. For example:
all: works-but-not-optimal quotes-cause-problems better also-okay
works-but-not-optimal: GREET = "\nHi! How are you?\n"
works-but-not-optimal:
@printf $(GREET)
quotes-cause-problems: GREET = "Hallo! Wie geht es Ihnen?"
quotes-cause-problems:
@printf "\n$(GREET)\n"
better: MSG = ¡Hola, buenos días!
better:
@printf "\n$(MSG)\n"
also-okay: export MSG = Buon giorno!
also-okay:
@printf "\n$$MSG\n"
In Makefiles, leading whitespace for top-level variable definitions is trimmed, but all remaining internal or trailing whitespace is preserved intact. That does mean, however, that you need to watch out for trailing whitespace (:set list
for the vi users) in variable definitions. It's never easy!
Make isn't great about handling filenames with spaces in them either, as targets or dependencies, so these are best avoided. You'll also have challenges if you need to represent quotes within quotes—good luck with those backslashes! Consider using typographic quotes instead, if they're just for display purposes.
By the way, all three examples above demonstrate target-specific variables as that phrase is used in the GNU Make manual.
Hope that helps (someone)!
* …unless you take steps to force multiple recipe lines to run in the same subshell, which is out of scope for this already-sprawling answer. ;)
Upvotes: 0
Reputation: 34823
I don’t think target-specific variables will help you here, because they’re usually static things. For example, if you need to silence one type of warning for one specific C file, you can add a rule like foo.o: CFLAGS+=-Whatever
.
The problem you’re running into is that $(eval header=...)
is only executed when $(file1)
is made. If it already exists, then the target won’t get rebuilt, and header
won’t get set.
A more natural way of doing this in a Makefile would be to save the header to a separate file. That way, it will automatically get regenerated whenever $(file)
changes:
.DELETE_ON_ERROR:
file = foo.txt
call_perl_script: $(file) $(file).header
echo perl script.pl in="$(shell cat $(file).header)"
$(file):
echo "col1;col2;col3;$$(head -c1 /dev/random)" > $(file)
%.header: %
sed -n '/^col1;col2;col3/p' $< > $@
clean::
rm -f $(file)
rm -f *.header
which results in:
echo "col1;col2;col3;$(head -c1 /dev/random)" > foo.txt
sed -n '/^col1;col2;col3/p' foo.txt > foo.txt.header
perl script.pl in="col1;col2;col3;?"
However this is still a bit of a kludge, so for long-term maintainability, you may want to consider updating script.pl
to parse out the header itself.
Upvotes: 2