When you type NAME=value in the terminal, a variable is created. However, the value might not be visible to the next program executed. On the other hand, if you declare it with export NAME=value, programs can read the value. The difference between the two is not in the form of the value, but in whether that value is passed to child processes.
The first sentence to remember is this:
VAR=valueis a shell variable used only by the current shell, andexport VAR=valueis an environment variable passed to subsequent child processes.
Here, a shell is a program like Bash or Zsh that interprets commands, and a process refers to a single running program. When you execute commands like python, node, or docker from the terminal’s shell, those programs start as child processes of the shell.

Variables that remain only within the current shell and environment variables passed to child processes
Variables are the same, but the passing indicator is different
In Bash, it can be confusing to think of shell variables and environment variables as existing in completely separate storage. Practically, it’s more accurate to understand that you create a shell variable, and then attaching the export attribute to it makes it available to child processes.
PROJECT_MODE=development
This command creates a variable named PROJECT_MODE in the current shell. The value can be read within the same shell.
printf '%s
' "$PROJECT_MODE"
# development
However, if you run a new Bash as a child process, the value is not passed.
bash -c 'printf "%s
" "${PROJECT_MODE-unset}"'
# unset
Now, let’s attach the export attribute to the existing variable.
export PROJECT_MODE
bash -c 'printf "%s
" "$PROJECT_MODE"'
# development
export PROJECT_MODE is less about copying a new value and more about indicating that this variable should be included in the environment of commands to be executed. The result is the same if you combine value assignment and export on one line, as shown below.
export PROJECT_MODE=development
The GNU Bash official documentation also explains that executed commands inherit the environment of the parent shell, and export marks a variable to be included in the environment of subsequent commands. The POSIX shell standard also defines that variables with the export attribute are included in the environment of subsequently executed commands.
It’s Easy to Understand by Viewing Parent Shells and Child Processes Like Family Relationships
Let’s think of the current terminal’s Bash or Zsh as the parent. If you run Python from this shell, Python is the child.
터미널의 셸
├── python app.py
├── node server.js
└── bash script.sh
A regular shell variable is a value that the parent has only noted in its own notepad. An exported variable is included in the list of environment variables given to a child when it is born. There’s one important limitation to this analogy: even if the child changes the received value, the parent’s value does not change in reverse.
export COLOR=blue
bash -c 'COLOR=red; printf "child=%s
" "$COLOR"'
printf 'parent=%s
' "$COLOR"
# child=red
# parent=blue
Process environments are fundamentally passed from parent to child. A child process is not a conduit for directly modifying the parent shell’s variable storage.
Comparison of Four Declaration Methods
| Declaration Method | Persists in Current Shell | Passed to Child Processes | Primary Use Case |
| — | — | — | — |
VAR=value |
Yes | No | Calculations/settings within the current shell |
export VAR=value |
Yes | Yes | Settings for multiple programs to be executed later |
VAR=value command |
No | Yes, only for that command | Temporary setting for a single command |
env VAR=value command |
No | Yes, only for that command | Explicit temporary environment configuration |
The third method is particularly useful in deployment and testing.
APP_ENV=test python app.py
python app.py receives APPENV=test, but after the command finishes, APPENV does not remain in the current shell.
APP_ENV=test bash -c 'printf "inside=%s
" "$APP_ENV"'
printf 'after=%s
' "${APP_ENV-unset}"
# inside=test
# after=unset
The GNU Bash manual explains that variable assignments preceding a command are added to the environment only for the duration of that command’s execution and do not affect the current shell environment. Specific rules may differ for shell functions and special builtins, so it’s best to understand the general case of executing external programs first.
Why are variables visible without `export` when executed with `source`?
The way a script is executed also affects variable visibility.
TOKEN=demo
./check.sh
./check.sh is executed as a separate child process. If TOKEN was not exported, the script typically would not receive this value.
On the other hand, the following command is different:
TOKEN=demo
source ./check.sh
source or the dot command . ./check.sh executes the commands in the file within the current shell without starting a new process. Therefore, it can read current shell variables, and any variables changed by the script also remain in the current shell.
Because of this difference, when activating a Python virtual environment, you use source as follows, rather than ./venv/bin/activate:
source .venv/bin/activate
This is because the activation script needs to modify the current shell’s PATH.
Exporting Does Not Mean Permanent Storage
It’s easy to misunderstand export as “permanent environment variable registration,” but export only applies to the current shell process and its children. When the terminal window is closed and the shell terminates, those settings also disappear.
To set variables every time a new shell is opened, you must declare them in the startup file of the shell you are using.
# Files commonly used in Bash interactive shells
export APP_HOME="$HOME/apps/example"
- In Bash,
~/.bashrc,~/.bash_profile,~/.profile, etc., are used depending on the situation. - For Zsh interactive configuration,
~/.zshrcis typically used. - The file read differs depending on whether it’s a login shell, an interactive shell, or a script execution.
After modifying a configuration file, you can source it to apply the changes immediately to the current shell.
source ~/.zshrc
However, if settings are declared redundantly across multiple files, it becomes difficult to track which file last overwrote the value. It’s best to keep settings in a single file appropriate for their purpose.
.env Files Do Not Automatically Become Environment Variables
Let’s assume you’ve created a .env file like this:
DATABASE_HOST=localhost
DATABASE_PORT=5432
The mere existence of the file does not mean the operating system or shell will automatically read it. An application’s dotenv library, Docker Compose, development tools, or separate shell code must read the file.
If you want to export all variables from a file containing assignment statements in the shell, you might see the following pattern in Bash:
set -a
source .env
set +a
set -a automatically attaches the export attribute to variables created or modified thereafter. However, there’s no guarantee that a .env file uses safe shell syntax. Sourcing an untrusted file is dangerous because its commands will also be executed in the current shell. For simple data files, it’s safer to use a dedicated .env parser provided by the tool you are using.
Commands for Checking Values Also Differ
If you want to see a broad range of current shell variables, you can use Bash’s set command. Since the output can be extensive and include functions, it’s usually better to query by knowing the name.
printf '%s
' "$PROJECT_MODE"
declare -p PROJECT_MODE
The list of exported variables can be checked as follows:
export -p
The environment received by external programs can be checked with env or printenv.
printenv PROJECT_MODE
env | grep '^PROJECT_MODE='
To remove only the export attribute of a variable while keeping its value in the current shell, you can use export -n in Bash.
export -n PROJECT_MODE
To remove the value itself, use unset.
unset PROJECT_MODE
Is it Safe to Export Passwords and API Keys?
Environment variables are not secret storage. While they are easier to separate from source code than embedding keys directly, they can be exposed to processes running with the same permissions, debugging tools, error collection, process dumps, CI logs, or poorly written diagnostic code. Child processes inherit exported secrets, so it’s best not to pass them more broadly than necessary.
If only one program needs a value, you can narrow the scope rather than exporting it globally for a long time.
API_TOKEN='temporary-value' ./deploy.sh
In production environments, dedicated methods such as cloud secret management services, Vault, Kubernetes Secrets, and short-lived credentials should be considered. Directly writing secrets on the command line can also leave traces in shell history, so secure injection paths must be used.
Furthermore, sudo can remove or restrict environment variables based on its security policy. You should not assume that a command executed with sudo will receive all values exactly as exported from the parent shell.
Four Common Misconceptions
1. “If it’s written in uppercase, it’s an environment variable”
No, that’s incorrect. Uppercase is merely a convention. MY_VALUE=test is a regular shell variable if not exported, even if it’s in uppercase.
2. “export is a command that copies values”
The key is to attach the export attribute to the variable. If you change the value later, the new value will be passed to the environment of the next child process.
export LEVEL=one
LEVEL=two
bash -c 'printf "%s
" "$LEVEL"'
# two
3. “If the child changes it, the parent also changes”
No, that’s incorrect. Inheritance is from parent to child. Changes made by a child are not automatically propagated back to the parent shell.
4. “If you export it, it remains after rebooting”
No, that’s incorrect. Export applies within the lifecycle of the current shell. Permanent settings must be recorded separately in shell startup files, service managers, container configurations, or deployment platform environment settings.
How to Choose in Practice
- For calculations only within a shell script:
VAR=value - If multiple subsequent programs need to read it:
export VAR=value - To temporarily provide it to a single command:
VAR=value command - If the script changes current shell settings:
source script.sh - If needed after reconnecting: record in the correct shell startup file or service configuration
- For secret values: minimize the scope of transmission and consider dedicated secret management tools
Ultimately, the meaning of export is not “to declare a variable more strongly.” It means to send the name and value, which were only in the current shell, to the environment of subsequently executed child processes. Remembering this one sentence will make it much easier to trace why environment variables are visible or not visible in .env, Docker, Python, Node.js, and CI/CD.
Leave a Reply