Issue
I have a Makefile such that one of the targets creates a .env
file, which is read in another target.
So, since the file is created dynamically, I can't include
it at the top of the Makefile. I tried the method using export
and xargs
, as described here, but it doesn't work. Here's my Makefile:
create_dotenv_file:
echo "FOO=BAR" > .env
test_env: create_dotenv_file
export $(cat .env | xargs) && \
echo $(FOO)
And this is what I get when executing $ make test_env
:
/home# make test_env
echo "FOO=BAR" > .env
export && \
echo
What am I missing?
Solution
There are several problems here:
the expression
$(cat .env | xargs)
isn't doing what you intend. Themake
command itself uses$
to variable expansion; this means thatmake
is interpreting that expression, rather than the shell. Tomake
it looks like a reference to an invalid variable so the replacement is the empty string.Similarly,
$(FOO)
is referring to themake
variableFOO
, which doesn't exist.You will need to replace
$
with$$
in order for the shell to see a single$
.Each line in a Makefile target executes in a new shell. Even if your
export $(cat .env|xargs)
statement works, it's a no-op -- changes to environment variables only impact the current process and its children. You are effectively starting a new shell, setting an environment variable, and then exiting the shell; your environment variables are lost.
You can rewrite your Makefile
so that it "works" by doing something like this:
create_dotenv_file:
echo "FOO=1" > .env
echo "BAR=2" >> .env
test_env: create_dotenv_file
export $$(xargs < .env); \
echo $${FOO}
But this is still problematic. The above sets two variables and works as expected, but what happens if one of your variables contains whitespace, like:
create_dotenv_file:
echo "FOO=\"This is \"" > .env
echo "BAR=\"a test\"" >> .env
You'll end up with the shell expression:
export FOO=This is BAR=a test
So you will have FOO=This
, BAR=a
, and a couple of empty environment variables named is
and test
.
A more effective solution might look something like this:
create_dotenv_file:
echo "FOO=\"This is \"" > .env
echo "BAR=\"a test\"" >> .env
test_env: create_dotenv_file
set -a; \
. ./.env; \
set +a; \
echo $${FOO}
This uses set -a
to enable the export
of all variables that we create or modify; then .
to read in the .env
file, and then set +a
to disable the set -a
. The output of running make test_env
with the above Makefile
is:
echo "FOO=\"This is \"" > .env
echo "BAR=\"a test\"" >> .env
set -a; \
. ./.env; \
set +a; \
echo ${FOO}
This is
But even with that solution, it probably still doesn't do what you want...variables sourced in the test_env
target won't be available anywhere else, because setting environment variables only impacts the current process and its children. Exporting variables in shell scripts simply cannot make them available elsewhere in the Makefile
.
Answered By - larsks Answer Checked By - Candace Johnson (WPSolving Volunteer)