TIL: loading .env files into my terminal
I commonly need environment variables for personal or work projects. In many
cases, they are loaded automatically from a .env file: Docker/Podman can take
a --env-file .env flag and many programming languages have a dotenv library
that reads this file automatically. But when I want to run one-off commands in
the terminal, I need to parse this file manually.
When it is just one environment variable, I just export FOO=bar and call it a
day. But when there are more variables, I get lazy. Also, these export
commands get saved in my shell history file which is very handy and very scary:
to get a variable I exported in the past I just hit CTRL-R and start typing it
until I recover the full command. But I don’t want a command with a secret
saved in plain text in any shell history file (or any file at all).
Here’s a little trick to load all env vars from a .env file:
$ export $(<.env)
This will read all lines of the .env file, and feed each word to the export
command.
For example, if you have this .env file:
CAT=Bob
OTHERCAT=Dimitri
The $(<.env) magic will produce CAT=Bob OTHERCAT=Dimitri, which is then
passed to export. This exports all those variables.
But note: this trick is not a general-purpose .env parser, it only works for
simple KEY=VALUE lines. Spaces, quotes, special characters, and “comments”
kind of break this command. For example, this .env file:
CAT=Bob
OTHERCAT=Dimitri
# What about turtles?
TURTLE=Sabrina
Leads to this:
export CAT=Bob OTHERCAT=Dimitri # What about turtles? TURTLE=Sabrina. Each
word is sent to export as an argument, which gives some errors:
$ export $(<.env)
-bash: export: `#': not a valid identifier
-bash: export: `turtles?': not a valid identifier
But we still get the TURTLE variable exported.
Another way to load a .env file in Bash is to enable the allexport option
and then source the file:
$ set -o allexport
$ source .env
$ set +o allexport
Disabling allexport afterwards is good to keep our sanity: allexport
controls whether new or modified variables get automatically exported.
Turning it off restores the default state.
Another advantage of allexport + source is that it allows us to define
“dynamic” environment variables: we can use shell expansions and run commands
to, for example, query a secrets store. A .env file is now a shell script,
much more powerful than a mere data file:
CAT=Bob
OTHERCAT=Dimitri
# What about turtles?
TURTLE=Sabrina
LUCKY_NUMBER=${RANDOM}
API_KEY=$(keepassxc-cli show -sa Password ~/.secrets/personal.kdbx '/AWS/groot-key')
The disadvantage of this more powerful file (i.e. a Bash script) is that it
breaks the export $(<.env) trick, and tools (e.g. docker run --env-file .env or a dotenv() function) that expect a simple KEY=VALUE file may
misbehave or crash completely. And because this executes the script, you
should only source files you trust.
PS: set -a is short for set -o allexport.
