If I have a list of files:
files := xx_foo1.c yy_foo2.c zz_bar1.c aa_bb_bar2.c
Is there any way of removing everything up to the last underscore from the list to get foo1.c foo2.c bar1.c bar2.c?
I was looking into using patsubst, but I would need two%'s -- one for the first part to be ignored, and one for the last part to be kept.
It can be done but it's a little gross. You want something like this:
final := $(foreach F,$(files),$(word $(words $(subst _, ,$F)),$(subst _, ,$F)))
This says, for each element in files we convert the _ to a space, now we can use our per-word functions to manipulate it: extract the last word in the list of words.
ETA
ReAl points out below that this can be simplified using lastword:
final := $(foreach F,$(files),$(lastword $(subst _, ,$F)))
As I see it you are using the underscore as separating character between hierarchical names. GNUmake is well equipped to work with such a scheme if the character is /: file name functions.
So your example should simply boil down to
$(notdir $(subst _,/,$(files))
Use the external program — sed — and enjoy all its power
files := xx_foo1.c yy_foo2.c zz_bar1.c aa_bb_bar2.c
f := `echo $(files) | sed -e "s/[[:alnum:]]*_//g"`
all:
echo $(f)
Related
I want to check if all the files that have a specific name have in them a string , if not to report them . I wrote this sequence and tried multiple some others, but I don't know how to access the contain of a file from list.
SOURCES := $(shell find $(SOURCEDIR) -name 'mod.mak')#here I take the list of targeted files (this works fine)
$(foreach File, Files,
$(if $(grep -q "aaabbb" "$File"),,#echo "WARNING Missing sequence")
)
You have multiple issues with your script.
First of all, you need some rule/target. For your example we can make a PHONY target test. Second, to iterate over values in SOURCES, you need to reference it as $(SOURCES). Similarly for $(file) in call to grep. Also, make's if is interpreting value, not exit code, so you shouldn't silence grep.
This will do it:
.PHONY: test
SOURCES := $(shell find "$(SOURCEDIR)" -name 'mod.mak')
test:
$(foreach file,$(SOURCES),$(if $(shell grep "aaabbb" "$(file)"),,#echo "WARNING Missing sequence in $(file)"))
I need to extract the word before the last from $(MAKEFILE_LIST).
So far I could not come up with anything better than this kind of monstrosity:
LIST := a b c
LAST_WORD_INDEX = $(words $(LIST))
BEFORE_LAST := $(word $(shell echo $(LAST_WORD_INDEX) - 1 | bc),$(LIST))
$(info word before last is $(BEFORE_LAST))
When I run it:
word before last is b
make: *** No targets. Stop.
The result is correct, but is there more elegant and sane way to achieve the same?
$(words ${LIST}) will give you the index of the last-but-one word if you prepend an element to LIST.
BEFORE_LAST := $(word $(words ${LIST}),1st ${LIST})
Notice that 1st in there.
I ended up using GMSL library, which makes things quite a bit more coherent:
include gmsl-1.1.6/gmsl
$(lastword $(call chop,$(MAKEFILE_LIST)))
I have a variable
SRC = file1.csv file2.csv file3.csv
If I echo $(subst .csv,.,$(SRC)) or echo $(subst csv,,$(SRC)), I'll get
file1 file2 file3
What I looking for is
file1. file2. file3.
But GNU make keep stripping the dot. How to solve this?
Add:
My real lines has extra things like this:
SRC = $(shell dir /b /s $(SRC_ROOT)\*.csv))
echo $(subst .csv,.,$(basename $(notdir $(SRC))))
EDIT: Thanks guys, MadScientist was right, I have some additional $(basename $(notdir in the line that causes the problem. Below is my real lines and with MadScientist's sharp eye and comment, my solution turn out like this:
SRC = $(shell dir /b /s $(SRC_ROOT)\*.csv))
echo $(patsubst %csv,%,$(notdir $(SRC)))
I agree with Etan, your example works fine. I think you're doing something different in real life than you've described here.
I'll note though that using subst is a bad idea. If you had a file like cacsv20.csv then subst on csv would do the wrong thing.
I suggest you use patsubst, such as: $(patsubst %csv,%,$(SRC))
ETA: based on the updated question, you probably want:
SRC := $(shell dir /b /s $(SRC_ROOT)\*.csv))
BASE := $(patsubst %csv,%,$(notdir $(SRC)))
Use the notdir to take off the directory name, and the patsubst (not basename) to remove the suffix, since you want to keep the trailing period.
Note I'm using := rather than =; except in very special situations you always want to use := when you use $(shell ...), for performance reasons.
Is there a possibility to remove duplicates in a list of words without sorting in a makefile?
$(sort foo bar lose)
does remove duplicates (which is for me the main functionality in this case), but also sorts (for me an unfortunate side effect in this case). I want to avoid that.
[update]
bobbogo's answer works very nicely. Just remember to use define uniq for v3.81 and (did not check this) define uniq = for later versions.
larsmans' answer works very nicely too if your record separator is not a space, e.g. if you want to remove duplicates from _foo_bar_lose_lose_bar_baz_ or the like. Just remember to use the RS and ORS awk options instead of tr, and wrap it all with $(firstword $(shell ... ))
Boring $eval based method:
define uniq =
$(eval seen :=)
$(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_)))
${seen}
endef
w := z z x x y c x
$(info $(sort $w))
$(info $(call uniq,$w))
Extremely fiendish make standard library recursive call (recursive make considered extremely fiendish?):
uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
It's worth noting that no variables are damaged in this second formulation (see seen in the first). It is preferable just for that (given the lack of locals in make)!
EDIT
My obscure comment about recursive make above seems to have muddied the waters somewhat.
"Recursive" in the context of this post means recursive function.
It really has nothing to do with the execrable recursive make.
The latter (recursive) definition of uniq is extremely nice, performant, small, and is definitely the one to use.
Depends on where you need it and whether you use GNU make. If you just want to uniq the list of target prerequisites, it's as easy as (http://www.gnu.org/software/make/manual/make.html#Quick-Reference) :
The value of $^ omits duplicate prerequisites, while $+ retains them and preserves their order.
So, a rule like
exe: $(OBJS)
$(LD) -o $# $^
will filter duplicates from $(OBJS) automagically, while still leaving order of other items the same.
You could echo the words through awk:
echo foo bar foo baz bar | tr ' ' '\n' | awk '!a[$0]++'
Deduping one-liner taken from catonmat.
(Don't forget to double the $ to $$ in a Makefile.)
The following works for me under GNU make v3.82:
uniq = $(eval _uniq := $1)$(strip $(foreach _,$(_uniq),$(if $(filter $_,$(_uniq)),$(eval _uniq := $(filter-out $_,$(_uniq)))$_)))
It doesn't modify its input by creating a copy in _uniq, and it's not recursive.
I am writing a Makefile and I get stuck on a filter function limitation.
Indeed, filter takes only one wildcard.
What I would like to do is:
I have a list a files, some matching the regexp blabla, some not. But for this I need 2 wildcards, thus i cannot use filter function.
I would like to split my original list in 2 lists, one containing all the element containing the blabla string (filter equivalent) and the other one containing the not matching one (filter-out equivalent).
thanks for your help.
You can do this without running any external commands. Define the two macros
containing = $(foreach v,$2,$(if $(findstring $1,$v),$v))
not-containing = $(foreach v,$2,$(if $(findstring $1,$v),,$v))
Now you can do
LIST := a_old_tt x_old_da a_new_da q_ty_we
LIST_OLD := $(call containing,old,$(LIST))
LIST_NOT_OLD := $(call not-containing,old,$(LIST))
One of Make's greatest shortcomings is its poor ability to handle regular expressions. The functions filter and filter-out can't find "old" in the middle of a word. I'd suggest this hack:
NOT_OLD = $(shell echo $(LIST) | sed 's/[^ ]*old[^ ]* *//g')
OLD = $(filter-out $(NOT_OLD), $(LIST))
You could take advantage of your shell's more advanced string handling capabilities. Assuming that you have bash, you could use the following in your makefile:
LIST := a_old_tt x_old_da a_new_da q_ty_we
LIST_NOT_OLD := $(shell l=($(LIST)); echo $${l[#]//*old*})
LIST_OLD := $(filter-out $(LIST_NOT_OLD),$(LIST))
You can find an explanation of the bash string replacement mechanism in how to delete elements from an array based on a pattern. The double $ is required to keep the $ sign in the shell invocation.