35.2 export: Marking Variables for Child Process Inheritance
Right, let’s talk about export. You’ve probably already set a variable like MY_VAR="some value" and then felt a profound sense of betrayal when you ran a script and it had absolutely no idea that MY_VAR existed. Welcome to the party. This is the entire reason the export command was invented.
Think of your shell session as a gated community. When you declare a variable with a simple assignment, it’s a private citizen of that community. It can’t leave, and nothing outside can see it. When you run another script or program (what we call a child process), it’s like spawning a new, separate gated community next door. By default, it doesn’t get a copy of your community’s private citizens.
The export command is you taking that private variable, giving it a passport, and saying, “Okay, any new communities we build from now on, make sure this guy gets a copy.” It marks the variable for automatic inheritance by any and all child processes you launch from that shell.
Here’s the classic, soul-crushing example:
# This variable is locked in your current shell
MY_PRIVATE_VAR="I am a secret"
# This script will print nothing, or worse, an empty string
./my_script.sh
# Now, let's give it a passport
export MY_PRIVATE_VAR
# Now the script will see it
./my_script.sh
The Two Ways to Skin a Cat (or Export a Var)
There are two syntactically different ways to do the exact same thing. The first is the separate command, which is what you see most often:
MY_VAR="value"
export MY_VAR
The second is a combined assignment and export, which is more concise and frankly, what I use 99% of the time because I’m lazy in the correct ways:
export MY_VAR="value"
Technically, you can also just type export MY_VAR without assigning a value first. This marks the variable for export but leaves it as an empty string (or “unset” if it didn’t exist already). This is occasionally useful, but mostly it’s just a weird footgun waiting to happen.
It’s a One-Way Street
This is the most important thing to internalize, so I’m going to put it in all caps: EXPORTING A VARIABLE ONLY AFFECTS CHILD PROCESSES, NOT PARENT PROCESSES.
Let’s say you export a variable and then run a script. That script gets a copy of the variable. If the script changes the value of that variable, it’s only changing its own personal copy. The moment the script finishes and returns control to your original shell, that changed value vanishes into the ether. Your original shell’s variable remains untouched.
# In your main shell:
export ORIGINAL_VAR="parent"
# Inside a script called 'child.sh':
#!/bin/bash
echo "Child received: $ORIGINAL_VAR"
ORIGINAL_VAR="child"
echo "Child changed it to: $ORIGINAL_VAR"
# Back in your main shell, after running the script:
echo "Parent sees: $ORIGINAL_VAR" # Still prints "parent"
The shell designers made this choice for a very good reason: sanity. If every script you ran could arbitrarily change the environment of your main shell, it would be absolute chaos. Processes are isolated for our protection.
The -n Option: Revoking the Passport
What if you change your mind? You’ve exported a variable but now you don’t want the next child process to inherit it. You use the -n option to revoke its passport.
export MY_VAR="value" # Marked for export
export -n MY_VAR # No longer marked for export
This doesn’t delete the variable! It just removes its “export” flag. The variable MY_VAR still exists and has its value in your current shell; it just won’t be passed to any new children.
Best Practices and the -p Flag
A quick pro tip: you can view all currently exported variables in your shell by simply typing export -p. The -p flag means “print,” and it’s the default behavior, so just typing export does the same thing. It’s incredibly useful for debugging when your environment feels like it’s having an identity crisis.
The best practice is to be ruthlessly minimalist with your exports. Don’t just export everything willy-nilly. Every exported variable is a global, and we all know how fun global state is to maintain. Only export what you are certain a child process will need. This keeps your environment clean and prevents weird, hard-to-debug conflicts where a program you run picks up a variable you forgot was even set years ago.
So, to sum up: export is your mechanism for explicitly defining the environment your children will inherit. Use it deliberately, understand it’s a one-way street, and for the love of all that is holy, don’t expect your parents to change just because you did.