Search code examples
shellmakefilerecipe

How to use a for loop in make recipe


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?


Solution

  • 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.