The values of environment variables can control the behavior of an operating system, individual utilities such as Git, shell scripts, user applications such as the Google Chrome browser, or deployed applications such as a Python web app.
Let's look at the basics for setting and accessing environment variables using the following commands:
That's only the tip of the iceberg, so let's dive deeper.
Simplistically, shell variables are local in scope whereas environment variables are global, but let's explore this further with examples.
Shell variables should be used when they are only needed in the current shell or script in which they were defined.
If you opened a new shell and ran the echo command, the NAME variable does not exist as it was scoped to the previous shell only.
The same rules apply to scripts, for example, if a file shell-var-test.sh contained the following:
And shell-var-test.sh was run even after NAME was defined, it's not accessible to the script.
Environment variables, on the other hand, are designed to be accessible to scripts or child processes and differ from shell variables by use of the export command.
It's also true that any variable changes inside a script or new process do not affect the shell where they were executed from, even for environment variables.
We delve deeper into the scope of environment variables in shells and shell scripts later in this article, as well as how to change the default scoping behavior by learning how to execute a script in the context of the current shell.
Real quick shoutout to Doppler. Tired of copying and pasting environment variables into your Vercel, Render, Heroku, etc and manually sharing your .env file with teammates, give Doppler a try to automate the pain away. Now back to the post!
Changing an environment variable is no different from changing a shell variable:
You can also modify a variable using its original value to create a new value. You've most likely seen this used before in a ~/.bash_profile or ~/.bashrc file when appending a directory to the $PATH variable:
Note that we did not put export before PATH in this example, and that's because PATH was already exported, so putting export again does nothing.
The unset command is used to remove a variable:
The deletion of an environment variable only affects the current shell.
Here are some rules and best practices for assigning and using environment variables:
It's common to capture the output of a command using command substitution $(...) and assign it to a variable. For example, assign the value of a date formatted string:
Using double or single quotes depends on your requirements and as a rule, always quote your values to avoid unintended word-splitting issues:
Double quotes allow the use of variable referencing and command substitution $(...), for example, getting the current username:
If using double quotes for variable referencing but also want to output the $ sign, then you need to escape it using the backslash \ character:
Another option instead of escaping a reserved symbol such as $ is using single quotes, as it prevents variable referencing and command substitution because the string is not interpolated:
Finally, if you're in a situation where the $VAR_NAME form doesn't work because of ambiguity, then you need the ${VAR_NAME} syntax instead:
Every command, script, and application runs in its own process, each having a unique identifier (typically referred to as PID). It may run for a few milliseconds (e.g. ls -la), or many hours, e.g. Visual Studio Code or a Python application server.
To see this in action, let's run a sleep command as a background process by appending & after the command so we see its PID in the shell:
When running a command or script from the shell, the current shell is the parent process and the command or script is a child process, which only has access to the environment variables from the current shell or parent process.
For security and isolation, any modifications to environment variables in a child process do not affect the parent process or any other shell sessions.
We can demonstrate this using the subshell (...) syntax or executing a string as a bash command:
In both instances, the SUBSHELL_VAR environment variable lived and died within the child process in which it was created and was not visible to the parent process.
It can be mind-blowing when you realize every single command, script, or application is a process, all the way up to PID 1 which is the process from which every other child process inherits from, even if not directly. You can use the pstree command (For Mac users: brew install pstree) to visually see this hierarchy in action.
Sometimes, you'll want to expose or change environment variables only for the life of a single command, script, or child process.
To demonstrate, we'll use the following script baby-yoda.sh:
First, let's look at how to do this the long and inefficient way:
We can instead turn this into a single command by defining the variable before command:
Now you might be wondering what if you want to temporarily expose an environment variable to multiple commands? It may not work the way you would expect. Let's try running our script twice using && syntax:
The second script invocation did not have the BABY_YODA environment variable set because the shell interpreted the above command as:
To temporarily expose an environment variable to multiple commands, we can pass a string to bash to execute:
At some point, you'll need to conditionally check for the existence of an environment variable.
In most situations, you'll want to check a variable is set with a non-empty value so that's what we'll start off with. To demonstrate, save the following code to deathstar-attack.sh:
Then let's execute the script without defining the DEATHSTAR_SHIELD_DOWN variable:
Now with defining DEATHSTAR_SHIELD_DOWN:
If on the other hand, you only want to check if a variable is set and don't care if it's empty or not, then use the following, saving it to death-star-attack-2.sh:
This works because the ${DEATHSTAR_SHIELD_DOWN+x} uses parameter expansion which for this purpose, evaluates to nothing if DEATHSTAR_SHIELD_DOWN is unset.
Where and how you set environment variables depends on the operating system and whether you want to set them at a system or individual user level, as well as what shell you're using, e.g. bash or zsh (which is now the default in Mac).
To avoid simply repeating content for the sake of it, check out this excellent comprehensive article from Unix/Linux Stack Exchange question: How to permanently set environmental variables.
The printenv command does exactly as it describes, printing out all environment variables available to the current shell.
You can combine printenv with grep to filter the list environment variables:
To output the value of a specific environment variable, you should use also printenv:
You might be thinking "can't you use echo for that"? And yes, you can, but echo works for shell variables too which makes printenv superior, because it only works for environment variables.
If you're wondering how to tell if printenv SOME_VAR actually worked or whether SOME_VAR was just empty, you can inspect the $? variable after the printenv command and if the exit code was 0, the environment variable exists.
It's possible to list both shell and environment variables using the set command:
You'll notice that you see not just variables, but functions too.
If using bash, you can use compgen which is less noisy:
The following environment variables are not an exhaustive list, but are the most common between Linux and Mac:
Environment variables are arguably the best way to supply config and secrets to deployed applications such as a Python or Node.js app.
This is because Virtual Machines (VM) and Docker containers provide a sandboxed environment for applications to execute in, therefore any environment variable only affects that specific application in that VM or container.
Because applications are configured differently from development to production, configuring an app using environment variables means only the values need to change when deploying to different environments, not the source code.
Environment variables should also be used to configure CI/CD jobs and environments, e.g, GitHub Actions.
Supplying application config and secrets using environment variables is precisely what the Doppler CLI does, and our mission is to make this as fast, easy, and secure as possible with integrations for every major every cloud provider and platform.
Most times, you'll want a script executed in a child process that can access, but can't modify the current shell. But there are scenarios where you'll want to load functions or variables from a script into the current shell.
An example is the bash autocompletion code from homebrew on macOS using the leading period syntax:
Let's demonstrate this functionality by loading variables into the current shell using the below script, saving it as current-shell-vars.sh:
Then make the script executable:
Simply executing the script will only make the variables available to the code inside the script:
To execute it in the current shell, we prefix the command with a leading period:
Be very careful with this functionality and only execute scripts you absolutely trust!
If you're wondering, "doesn't source do the same thing?", yes it does, but it's only available in bash, so the above form is simply more portable
By default for security, commands run as root using sudo do not pass through environment variables from the current shell, but you can override this using the --preserve-env flag:
Use this caution!
Sometimes, you'll want to execute a command or script in a "clean" environment, devoid of all environment variables using env -i:
To make an environment variable (or function) read-only, just use the readonly command:
There's obviously a lot more to shell programming than just environment variables and here are three essential resources to take your shell usage and scripts to the next level:
Awesome work! You now know a ton of great and practical stuff about environment variables for Linux and Mac and we hope you enjoyed the journey!
If you want thinking about environment variables to be a thing of the past, go on easy mode with Doppler.
Feedback is welcome and you can reach us on Twitter our Community forum.
Trusted by the world’s best DevOps and security teams. Doppler is the secrets manager developers love.