1. Overview

A Makefile is a file that contains a set of rules and commands for building a project. When the make program processes a Makefile, it executes the commands according to the dependencies and targets specified in that Makefile.

In this tutorial, we’ll understand the meaning and usage of $$ in a Makefile with examples to illustrate them.

2. Problem Statement

One of the features of a Makefile is that it enables the use of variables, i.e., placeholders for values that we can alter. Moreover, we can define and reference variables in a Makefile:

$ (name)

Alternatively, we can use {}:

$ {name}

In this case, name is the name of the variable.

However, sometimes we may want to use $ as a literal character, not as a part of the reference. For example, we may want to use $ in a shell script or a regular expression embedded in a Makefile. In this case, we need to escape $ by doubling it, so make doesn’t interpret it as a variable reference.

3. How make Processes a Makefile

To understand why we need to escape $ in a Makefile, we need to know how make processes a Makefile.

make reads and evaluates a Makefile in two phases:

  • read-in phase
  • target-update phase

In the read-in phase, make reads the entire Makefile and expands all the variable references and function calls in the file. The result is a data structure that represents the rules and commands in the Makefile. In the read-in phase, make doesn’t execute any commands or update any target.

On the other hand, in the target-update phase, make determines which targets need to be updated and executes the commands associated with them. In this phase, make doesn’t read or expand any more variable or function calls in the Makefile.

Additionally, the distinction between the two phases is important because it affects how the dollar sign is interpreted by make. In the read-in phase, $ is used to introduce a variable reference or a function call, and it’s expanded by make. Also, in the target-update phase, $ is passed to the shell as a literal character, and it’s interpreted by the shell.

4. Escaping the Dollar Sign in a Makefile

As we’ve seen, the dollar sign has a special meaning for both make and the shell. Therefore, if we want to use $ as a literal character in a Makefile, we need to escape it by doubling it.

When make encounters $$ in the read-in phase, it replaces it with $ and passes it to the data structure. Similarly, when make executes a command that contains a $ in the target-update phase, it passes it to the shell as a literal character.

4.1. Double Dollar Sign

For example, let’s suppose we have a Makefile that contains a rule:

echo:
    echo $$HOME

In the read-in phase, make expands $$ to $ and stores the command echo $HOME in the data structure. In the target-update phase, make executes the command echo $HOME and passes it to the shell. Then shell expands the variable $HOME to the value of the user’s home directory and prints it to the standard output.

4.2. Single Dollar Sign

Similarly, we may omit $$ and write the rule with a single dollar sign:

echo:
    echo $HOME

In the read-in phase, make tries to expand the variable $HOME and replaces it with its value in the Makefile. If the variable $HOME isn’t defined in the Makefile, make replaces it with an empty string. In the target-update phase, make executes the command echo and passes it to the shell. The shell then prints an empty line to the standard output.

5. Practical Double Dollar Sign Uses in a Makefile

Let’s look at some instances of using the double dollar sign in a Makefile for different purposes.

5.1. Shell Variables

Sometimes, we may want to use a shell variable in a Makefile, such as $PATH, $USER, $HOSTNAME. In this case, we need to use $$ to reference the shell variable, so that make doesn’t expand it in the read-in phase, and the shell expands it in the target-update phase.

For example, let’s suppose we have a Makefile that contains this rule:

whoami:
    echo $$USER

In the read-in phase, make expands $$ to $ and stores the command echo $USER in the data structure. In the target-update phase, make executes the command echo $USER and passes it to the shell. The shell then expands the variable $USER to the name of the current user and prints it to the standard output.

5.2. Regular Expression

Let’s suppose we want to write a rule in a Makefile that finds all the files in a current directory that end with .txt and prints their names.

We can use the shell command find with a regular expression as an argument. The regular expression for matching files that end with .txt is .*\.txt$. So, we might write a simple target in our Makefile:

find-txt:
    find . -regex '.*\.txt$'

However, we might not get the result we expect. This is because make tries to expand the variable $ in the Makefile, which isn’t defined. Therefore, make replaces $ with an empty string, and the command find .-regex ‘.*\.txt$’ becomes find .-regex ‘.*\.txt’, which matches files that contain .txt anywhere in their names, not just at the end.

To fix this, we need to escape the $ in the Makefile, so that make passes it to the shell. We can do this by writing $$ in the Makefile:

find-txt:
    find . -regex '.*\.txt$$'

Now, make ignores the first $ and treats the second one as a literal character. Therefore, make passes the command find . -regex ‘.*\.txt$$’ to the shell, which matches files that end with .txt as we expect.

5.3. make Variable in a Command Argument

make variable is a name that represents a value that can be changed or computed by make. So, another example is a rule in the Makefile that runs a program that expects a make variable as an argument.

For example, let’s say we have a script called makevar that prints the value of the make variable we pass to it as an argument.

First, we run it from the command line:

$ make -f Makefile FOO=bar
$ ./makevar FOO
bar

The code above performs several steps:

  1. make -f Makefile FOO=bar runs make with a specific Makefile and a make variable assignment
  2. -f tells make to use the Makefile we specify after it, instead of the default one
  3. FOO=bar tells make to define the make variable FOO with the value bar, before reading the Makefile

Therefore, ./makevar FOO prints bar, because we’ve defined the make variable FOO with the value bar in the command line.

However, if we want to run this program from the Makefile, we can’t write it in a similar manner:

run-makevar:
    ./makevar FOO

This won’t work, because make tries to expand the variable FOO in the Makefile, which we didn’t define. Therefore, make replaces FOO with an empty string, and the command ./makevar FOO becomes ./makevar, which prints nothing.

To fix this, we need to escape $ in the Makefile, so that make passes it to the program. We can do this by using $$ in the Makefile:

run-makevar:
    ./makevar $$FOO

Notably, we add $$ where $ doesn’t exist before because we want to pass the name of the make variable, not its value, to the program.

Now, make ignores the first $ and treats the second one as a literal character. Therefore, make passes the command ./makevar $$FOO to the program, which prints the value of the variable FOO, i.e., bar.

6. Escaping Other Special Characters in Makefile

Additionally, a Makefile doesn’t require us to escape many characters. However, some characters have a special meaning and need to be escaped by a ^ (caret).

Let’s look at some common ones:

Character

Meaning

Escaped Form

Comment

^#

%

Pattern matching

^%

:

Rule separator

^:

;

Command separator

^;

=

Variable assignment

^=

\

Line continuation

^\

(

Substitution or function

^(

)

Substitution or function

^)

Quotation mark

^”

Quotation mark

^’

This way, make treats them as literal characters and not as part of the syntax or variable.

7. Conclusion

In this article, we learned the meaning of the double dollar sign in a Makefile.

We saw that the purpose of the double dollar sign is to escape a dollar sign so that make doesn’t interpret it as a variable. Also, we looked at some examples of using a double dollar sign in a Makefile to pass a dollar sign to a shell command, a regular expression, or a program.

Finally, we understood how we can escape other special characters in a Makefile.