11

I would like to use a loop to find some files and rename them:

  for i in `find $@  -name *_cu.*`;do mv $i "$(echo $i|sed s/_cu//)"
  done

This works in the shell. But how can I do this in a makefile recipe?

Chad
  • 1,750
  • 2
  • 16
  • 32
Steve Wang
  • 335
  • 1
  • 3
  • 11
  • 1
    Off-topic: it would be a good idea to quote `'*_cu.*'` within the find command, as you want that expanded by find, not the shell that is invoking find. – John Marshall Jan 06 '12 at 13:41
  • The triggering of this command won't work right with this `$@` as your target. – reinierpost Jan 06 '12 at 20:59

3 Answers3

34

There are two main things you need to know when putting non-trivial shell fragments into make recipes:

  1. Commands in the recipe are (of course!) executed one at a time, where command means "tab-prefixed line in the recipe", possibly spread over several makefile lines with backslashes.

    So your shell fragment has to be written all on one (possibly backslashed) line. Moreover it's effectively presented to the shell as a single line (the backslashed-newlines are not plain newlines so are not used as command terminators by the shell), so must be syntactically correct as such.

  2. Both shell variables and make variables are introduced by dollar signs ($@, $i), so you need to hide your shell variables from make by writing them as $$i. (More precisely, any dollar sign you want to be seen by the shell must be escaped from make by writing it as $$.)

Normally in a shell script you would write separate commands on separate lines, but here you effectively only get a single line so must separate the individual shell commands with semicolons instead. Putting all this together for your example produces:

foo: bar
    for i in `find $@  -name *_cu.*`; do mv $$i "$$(echo $$i|sed s/_cu//)"; done

or equivalently:

foo: bar
    for i in `find $@  -name *_cu.*`; do      \
        mv $$i "$$(echo $$i|sed s/_cu//)";    \
    done

Notice that the latter, even though it's laid out readably on several lines, requires the same careful use of semicolons to keep the shell happy.

John Marshall
  • 6,815
  • 1
  • 28
  • 38
  • Out of curiosity, why do you leave `$@` intact? Doesn't it have a special meaning in Makefiles? In the shell, it expands to the list of script arguments... – fge Jan 06 '12 at 13:03
  • @fge: make passes the command to the shell as if via `system()`, so there are no shell script arguments and the shell's `$@` would always be empty. OTOH make's `$@` refers to the target of the rule (here _foo_), which I assumed was what was intended. – John Marshall Jan 06 '12 at 13:37
  • 1
    Well, I'm not that sure, the fragment from the OP seems indeed to be from a shell script – fge Jan 06 '12 at 13:38
5

I found this useful, trying to use for loops to build multiple files:

PROGRAMS = foo bar other

.PHONY all
all: $(PROGRAMS)
$(PROGRAMS):
    gcc -o $@ $@.c

It will compile foo.c, bar.c, other.c into foor bar other executables

fedeb
  • 122
  • 1
  • 4
0

I spend good time on this and finally had it working. I had an easy solution using the global variable in makefile available for all targets, however I don`t want that so this is how I did it.

target:
    $(eval test_cont=$(shell sh -c "docker ps | grep test" | awk '{print $$1}'))
    for container in $(test_cont);do \
        docker cp ssh/id_rsa.pub $${container}:/root/.ssh/authorized_keys; \
        docker exec -it $${container} chown root.root /root/.ssh/authorized_keys; \
    done
Dry_accountant_09
  • 1,371
  • 16
  • 15