Bash gets a lot of its power from environment variables, but what most people don’t realize is that the shell doesn’t actually store the variables; it just knows where to find them in memory.

Let’s see how this plays out. Imagine you have a script that needs a configuration value, like a database password. Instead of hardcoding it, you set an environment variable:

export DB_PASSWORD="mysecretpassword123"

Now, any process launched from this shell session will inherit DB_PASSWORD. If you have a Python script that accesses it:

import os
db_pass = os.environ.get('DB_PASSWORD')
print(f"Connecting with password: {db_pass}")

Running python your_script.py from the same terminal will output:

Connecting with password: mysecretpassword123

This inheritance is key. When a child process is created (e.g., running a command, launching a script), the operating system copies the parent’s environment table. This is why export is crucial – it marks the variable to be included in the environment passed to child processes. If you just did DB_PASSWORD="mysecretpassword123" without export, the variable would only exist within the current shell’s scope, not for any commands you run from it.

The mental model here is a tree. Your initial shell is the root. When you launch a new command or script, it becomes a child node. Each child inherits the environment of its parent. You can see the environment of a process using env or printenv. For example, in your current shell:

printenv | grep DB_PASSWORD

This will show DB_PASSWORD=mysecretpassword123. If you were to launch a subshell:

bash

And then run printenv | grep DB_PASSWORD again, you’d see the same output. However, if you were to unset DB_PASSWORD in the subshell and then exit, returning to the parent shell, printenv | grep DB_PASSWORD would still show the variable. This is because the unset only affected the child’s environment.

The practical application is vast. Think about API keys, database connection strings, logging levels, or even paths to custom executables. Instead of scattering configuration across dozens of files, you can centralize it in environment variables, making deployments and management much simpler. For instance, a web server might read PORT and DATABASE_URL from its environment.

The exact levers you control are primarily export, unset, and variable assignment. You can also use env -i to run a command with a completely clean environment, ignoring inherited variables, which is useful for testing or ensuring isolation.

env -i MY_VAR=hello bash -c 'echo $MY_VAR'

This will output hello. If you omit -i, it would likely output nothing unless MY_VAR was already exported in the parent shell.

Many tools and frameworks look for specific environment variables by convention. For example, JAVA_HOME tells the Java runtime where to find its installation, and many CI/CD systems rely on variables like CI_COMMIT_SHA or BUILD_NUMBER.

What trips people up is the scope and lifetime. Environment variables set in a non-interactive script are ephemeral; they vanish when the script finishes. To make them persistent across reboots or new shell sessions, you need to add the export command to a shell startup file like ~/.bashrc (for interactive shells) or ~/.profile (for login shells). The distinction between these files is subtle but important: ~/.bashrc is sourced for interactive, non-login shells (like opening a new terminal window), while ~/.profile is sourced for login shells (like SSHing into a server).

The way shell expansion and variable substitution interact with environment variables can be a source of confusion, especially when dealing with complex quoting or subshells. For instance, a variable exported in a subshell doesn’t automatically propagate back up to the parent.

This mechanism is also how tools like sudo can sometimes reset your environment, or how you can explicitly pass a limited set of variables to a command using VAR=value command.

Understanding the parent-child relationship and the role of export is fundamental to managing application configurations and system behavior effectively.

The next thing you’ll likely want to explore is how to manage these variables more systematically, perhaps using tools that allow you to load them from .env files into the shell environment.

Want structured learning?

Take the full Bash course →