1

In my Makefile I am trying to copy a list of files from location1 to location2, then to location2 to location3. I got the following strange behavior:

    FILES_LIST=dir1/file1 dir2/file2 dir3/file3 ........

    mytarget:
        for file in $(FILES_LIST) ; do \
            #this works
            cp -vf location1/$$file location2/$(shell $$file##*/) ; \
            #this does not work
            cp -vf location2/$(shell $$(file)##*/) location3/ ; \
        done

I am using "$(shell $$(file)##/)" to strip out "dir1/" part of each item in FILES_LIST. The first cp works (from location1 to 2), however, the send does not, build log shows "$(shell $$(file)##/)" is evaluated to empty.

I am using GNU Make 3.81

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148

2 Answers2

3

The problem is $$(file). That's not a variable evaluation. That's a Command Substitution. You meant $${file} (well not quite but we'll get to that).

There is also absolutely no reason to be using $(shell) here at all as you are already in a shell context when those lines run.

Not to mention that those $(shell) calls aren't doing anything even remotely like what you want (they aren't operating at the right time to do that).

You want this:

FILES_LIST=dir1/file1 dir2/file2 dir3/file3 ........

mytarget:
    for file in $(FILES_LIST) ; do \
        #this works
        cp -vf location1/$$file location2/$${file##*/} ; \
        #this does not work
        cp -vf location2/$${file)##*/} location3/ ; \
    done

Your $file variable is a shell variable not a make one. The call to $(shell) does not see it. You are effectively running $(shell $file##*/) which runs the $file##*/ command through the shell. That shell has no $file variable so that becomes ##*/ which is a comment and the whole thing returns nothing. (Actually I think the comment may be stripped first but that doesn't change anything.)

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • You are right on the spot sir! Thanks for the detailed explanation. "cp -vf location2/$${file##*/} location3/ ; \" worked for me – user3894299 Mar 10 '15 at 01:30
0

Use $(notdir $file) command. notdir will strip out the directories and return the filename. For example, $(notdir dir1/file.h) will return file.h

Reference link for more detailed info: https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html

Vivek
  • 320
  • 1
  • 5
  • the problem is that the $$file is evaluated to an empty value if I put it in parentheses: here is the log shows : for header in dir1/file1 dir2/file2 dir3/file3; do \ cp -vf /location2/ /location3 ; \ done cp: omitting directory '/location2/' – user3894299 Mar 10 '15 at 00:39
  • since file is defined in your makefile but not in the shell env, $$(file) becomes $(file) in the shell and hence evaluates to NULL. Use a single $ instead of $$, i.e. "cp -vf location2/$(shell $(file)##*/) location3/" – Vivek Mar 10 '15 at 01:10
  • `$file` is a shell variable not a make variable. – Etan Reisner Mar 10 '15 at 01:12
  • This answer is heading the wrong direction. This needs to move more towards the shell and not away from it. The `$(notdir)` function doesn't have anything to operate on in make context. – Etan Reisner Mar 10 '15 at 01:14
  • notdir should be used before the make rule, like this: FILES_ONLY=$(notdir $(FILES_LIST)). Now, FILES_ONLY will contain just the file names and in the make rule, FILES_ONLY should be used in the loop. – Vivek Mar 10 '15 at 19:07