2

I encounter the following sed command in make's manual.

sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g'

And it has the following effect (this is an example in the manual):

main.o : main.c defs.h
# is turned into:
main.o main.d : main.c defs.h

In my understanding, '\($*\)\.o' is intended to match the file name filename.o, but I do not understand how '\($*\)' achieve this. I know $ is special at the end of a line in a regular expression, but this does not apply here.

So what does '\($*\)' mean here?


The full code:

%.d: %.c
        @set -e; rm -f $@; \
         $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
         sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
         rm -f $@.$$$$

2 Answers 2

10

The $ used here is the dollar sign of the $* automatic Make variable, not something that denotes the end of line.

You have a similar example of this in the replacement part of the sed substitution, where $@ is used.

Make will substitute in the values of these variables in the sed expression before executing the sed command.

The use of \($*\) in the regular expression means the "stem" of the Make target (the string main in the example) is captured, and that it can later be reused as \1 in the replacement text.

The following sed command would have had the same effect, but without using a capture group:

sed 's,$*\.o[ :]*,$*.o $@ : ,g'
1
  • I cannot help thinking that this example is quite fragile; if a filename is injected as-is into a sed regex, then many normal filename characters (including .) become meaningful. Better hope that there is not another recipe for a file named mainio (which would be matched by main.o), and similarly that we don't have any filenames with commas in them! Commented Jul 3, 2024 at 8:51
6

It's important to keep in mind that normally regular expressions given on the command line to sed are subject to two rounds of parsing: once by sh (which is why you put in in ' quotes), and again by sed itself (which is why you put \ before ( and ) when you want them to control grouping).

When you put something inside a Makefile, there is a third round of parsing before those, by make.

sed only sees $ as end of line if the $ survives the previous two rounds of parsing.

In a Makefile, $ tells make that the following text is a variable (or function) to be expanded; if the next symbol is ( or {, everything up to the next matching ) or } will be used as the variable or function name; otherwise just the next symbol alone will be the variable name.

  • $(FOO)BAR expands to the contents of the variable FOO, followed by BAR;
  • $FOOBAR expands to the contents of the variable F, followed by OOBAR;
  • $* expands to the contents of the special variable called *, that contains the current target, and
  • $$ expands to the contents of the special variable called $, that simply contains a literal $; this is so that you can feed a $ to the shell (the second parsing phase) by writing $$ in your Makefile.
    • $$foobar expands to a single $ followed by foobar; and then the shell expands that to the contents of the shell variable foobar (but only if it's not single-quoted). Shell variables and Makefile variables are separate by default; do not assume the same name refers to the same value in both contexts.

(Old versions of make only support $(...) and not ${...}, so use the former for maximal portability.)

2
  • So $@.$$$$ becomes <target>.$$ (after make handled it) and then <target>.<pid> (after the shell handled it)? Commented Jul 2, 2024 at 16:15
  • @jcaron Correct Commented Jul 3, 2024 at 23:02

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.