I want something like this (date (from shell) in the target filename)
bakfile=mybackup.$(date +%Y%m%d).tar
$(bakfile):
tar -cf $# /source-dir
Or maybe with the date in the target spec?
mybackup.$(date +%Y%m%d).tar:
tar -cf $# /source-dir
A makefile is not a shell script. A makefile can contain shell scripts, but you can't just write shell operations directly anywhere in a makefile and expect it to work like the shell.
I recommend:
date := $(shell date +%Y%m%d)
then use the variable $(date) wherever you want the date string. Running it multiple times is a race condition risk, if you run it right around midnight the value might change between two different invocations.
You have to make a slightly greater effort, if you want to invoke shell commands from within a makefile:
bakfile=mybackup.$(shell date +%Y%m%d).tar
You can do it in the target name too, but I advise against that-- it's hard to read.
Related
Say in ~/prj/abc/abcsim/abctsim/abcxyz/Makefile there is a line below.
TOOLCHAIN_DIR := $(PWD)/../../../prj1/tools/gcc_tools
If I'm in directory ~/test, and if I run make -C ~/prj/abc/abcsim/abctsim/abcxyz, this doesn't work because the $(PWD) variable is set to ~/test, not ~/prj/abc/abcsim/abctsim/abcxyz. How can I get the directory path where the Makefile exists?
In bash there's something for this : How can I get the source directory of a Bash script from within the script itself?
If you really use make -C (not make -f) and your Makefile is not included in another, you can simply use the CURDIR variable. GNU make sets it to the absolute path of the current directory when it starts, "after it has processed any -C options". So, in your case it should do exactly what you want.
Else, if you sometimes use make -f or if you have included Makefiles, you can put this as the first line of any of your Makefiles (or, at least, before any include statement):
HERE := $(dir $(lastword $(MAKEFILE_LIST)))
and then use $(HERE) to refer to this Makefile's directory. See the GNU make manual for the details.
Note: I was almost sure this question would be a duplicate. Surprisingly I searched SO for a clear answer and found only old answers that first suggest shell calls before using make built-ins or wrong answers (using firstword instead of lastword, for instance).
I am attempting to write a shell script that will take a file name with a wildcard, find all files matching that pattern in current directory, and copy them. My problem is every time I try and use a variable only the first match echo's and thats it.
./copyfiles.ksh cust*.txt
#! /usr/bin/ksh
IN_FILE=${1}
for file in $IN_FILE
do
echo "$file"
done
cust1.txt
This seems to only match the first one even though cust1.txt, cust2.txt, and cust3.txt all exist and when I run it with for file in cust*.txt it works.
The shell expands your argument of "cust*.txt" to a list then passes the list to your script, which then only processes $1 which is cust1.txt.
You want to use $# which will process all arguments passed:
#! /usr/bin/ksh
for file in "$#"
do
echo "$file"
done
I believe there is a limit to how many arguments can be passed this way though. How many files are you having to process? Make sure your version of the shell can handle the number of arguments you are likely to process. If I recall you may need a solution utilizing xargs but I'm a tad rusty to help with that.
In ./copyfiles.ksh cust*.txt the files cust*.txt will be expanded first.
When you do not want to change your copyfiles.ksh script. call it with
./copyfiles.ksh "cust*.txt"
You can also change your script, with something like
IN_FILE="$#" # INFILES would be a better name
This question already has an answer here:
command substitution doesn't work with echo in makefile [duplicate]
(1 answer)
Closed 6 years ago.
Inside of a makefile, I'm trying to check if fileA was modified more recently than fileB. Using a few prior posts (this and this) as references, I've come up with this as an attempt to store the time since last file modification as a variable:
(I'd rather this be in a function outside of a make recipe, but one step at a time.)
.PHONY: all clean
all : (stuff happens here)
radio :
BASE_MOD_TIME="$( expr $(date +%s) - $(date +%s -r src/radio_interface/profile_init.c) )"
#echo "$(BASE_MOD_TIME)"
I thought that I would be assigning the output of the expr command to a variable, BASE_MOD_TIME, but the output is:
bash-4.1$
BASE_MOD_TIME=""
echo ""
What am I doing wrong here? Simple attempts to save the output of ls -l also didn't work like this.
Make variables are normally global, and you don't normally set make variables in a recipe. A recipe is simply a list of commands to be executed by a shell, and so what looks like a variable assignment in a recipe is a shell variable assignment.
However, each line in a make recipe is run in its own shell subprocess. So a variable set in one line won't be visible in another line; they are not persistent. That makes setting shell variables in recipes less useful. [Note 1]
But you can combine multiple lines of a recipe into a single shell command using the backslash escape at the end of the line, and remembering to terminate the individual commands with semicolons (or, better, link them with &&), because the backslash-escaped newline will not be passed to the shell. Also, don't forget to escape the $ characters so they will be passed to the shell, rather than being interpreted by make.
So you could do the following:
radio:
#BASE_MOD_TIME="$$( expr $$(date +%s) - $$(date +%s -r src/radio_interface/profile_init.c) )"; \
echo "$$BASE_MOD_TIME"; \
# More commands in the same subprocess
But that gets quite awkward if there are more than a couple of commands, and a better solution is usually to write a shell script and invoke it from the recipe (although that means that the Makefile is no longer self-contained.)
Gnu make provides two ways to set make variables in a recipe:
1. Target-specific variables.
You can create a target-specific variable (which is not exactly local to the target) by adding a line like:
target: var := value
To set the variable from a shell command, use the shell function:
target: var := $(shell ....)
This variable will be available in the target recipe and all dependencies triggered by the target. Note that a dependency is only evaluated once, so if it could be triggered by a different target, the target-specific variable might or might not be available in the dependency, depending on the order in which make resolves dependencies.
2. Using the eval function
Since the expansion of recipes is always deferred, you can use the eval function inside a recipe to defer the assignment of a make variable. The eval function can be placed pretty well anywhere in a recipe because its value is the empty string. However, evaling a variable assignment makes the variable assignment global; it will be visible throughout the makefile, but its value in other recipes will depend, again, on the order in which make evaluates recipes, which is not necessarily predictable.
For example:
radio:
$(eval var = $(shell ...))
Notes:
You can change this behaviour using the .ONESHELL: pseudo-target, but that will apply to the entire Makefile; there is no way to mark a single recipe as being executed in a single subprocess. Since changing the behaviour can break recipes in unexpected ways, I don't usually recommend this feature.
What's wrong with this?
fileB: fileA
#echo $< was modified more recently than $#
Instead of forcing the makefile to do all of the heavy lifting via some bash commands, I just called a separate bash script. This provided a lot more clarity for a newbie to bash scripting like myself since I didn't have to worry about escaping the current shell being used by make.
Final solution:
.PHONY: all clean
all : (stuff happens here)
radio :
./radio_init_check.sh
$(MKDIR_P) $(OBJDIR)
make $(radio_10)
with radio_init_check.sh being my sew script.
How can I use shell parameter extension in a Makefile?
I need to get the path to the leveldb database.
What I tried so far is in my Makefile:
$(shell db=$(locate leveldb/db.h); export LEVELDB_PATH=${db%%/include/leveldb/db.h})
LEVELDB_LIBS=-L$(LEVELDB_PATH) -I$(LEVELDB_PATH)/include -lleveldb
But LEVELDB_PATH is empty.
Thanks for help.
The shell cannot export stuff into your Makefile. Try something like this instead.
db := $(shell locate leveldb/db.h)
LEVELDB_PATH:=$(patsubst %/include/leveldb/db.h,%,$(db))
... or, to spare the temp variable,
LEVELDB_PATH:=$(shell locate leveldb/db.h | sed 's%/include/leveldb/db.h$$%%')
Edit: Fix to use $(patsubst) instead of $(subst), and double the dollar sign in the sed script.
Generally speaking, whatever goes on inside $(shell ...) happens in a subprocess, which (as ever) cannot modify its parent. make sees the output of the function when it finishes, but not what happened during the execution of the shell commands.
Is there a way to echo the (system, user, real) time spent in each target of a Makefile recursively when I do make all?
I'd like to benchmark the compilation of a project in a more granular way than just time make all. Ideally, it would echo a tree of the executed target, each one with the time spent in all its dependencies. It'd be great also if it could work with -j (parallel make). And by the way my Makefile is non-recursive (doesn't spawn another make instance for each main targets).
Thanks!
Gnu Make uses the $(SHELL) variable to execute commands in the targets.
By default it is set to /bin/sh.
You can set this variable to a script that will execute the command given with the "time" command. Something like this:
In your makefile specify the SHELL variable, somewhere at the top:
SHELL = ./report_time.sh
and in the file ./report_time.sh:
#!/bin/sh
shift # get rid of the '-c' supplied by make.
time sh -c "$*"
The replace the 'sh' command with the original SHELL specified in the Makefile if any.
This will report the timings.
However This will not tell you what target the report_time.sh script is running. One solution for this is to prepend the target name ($#) in each target entry in the makefile so that it will be passed to the report_time.sh script as well.
remake --profile is a drop-in replacement for make. It generates a target call tree in a callgrind format.