Why my exports didn’t persist after running a shell script Link to heading

I ran into this while writing a small setup script. I’d set a variable inside it, run the script, and then echo it in my terminal only to get nothing back. Empty. Every time.

Took me a while to realize source and export are two different things solving two different problems. Once that clicked, the whole thing made sense.

Running a script starts a fresh shell Link to heading

When you do this:

./setup.sh

Bash forks a brand new shell, runs your script in there, and throws that shell away when it’s done. Anything you set inside lives and dies in that child. Your terminal never sees it.

Here’s the script I was messing with:

# setup.sh
NAME=khizer
$ ./setup.sh
$ echo "$NAME"

Nothing. NAME was set, sure, but only inside that throwaway shell.

source runs it right here instead Link to heading

The fix was source (or its shorthand, the dot):

source setup.sh
. setup.sh

No new shell. Bash runs each line of the script inside my current shell, so the variable sticks around.

$ source setup.sh
$ echo "$NAME"
khizer

The way I remember it now: ./setup.sh means “go do this over there,” and source setup.sh means “do this right here.”

export is a separate question Link to heading

source was only half my confusion. The other half was export.

A plain assignment makes a variable that only your current shell can see. Child processes you launch won’t know about it:

$ NAME=khizer
$ bash -c 'echo "$NAME"'

Empty again. That inner bash is a child process, and it never got NAME.

export fixes that. It marks the variable so every child process gets a copy:

$ export NAME=khizer
$ bash -c 'echo "$NAME"'
khizer

The mental model Link to heading

Think of two shells: your host shell (the terminal you sit in) and the child shell a script spawns. Variables flow downward only.

   host shell        export NAME=khizer
        |  launch a child (copies the env)
        v
   child shell       NAME=khizer  ->  child sets NAME=ali
        |                             (only the copy changed)
        v  child exits
   host shell         still NAME=khizer

The host hands the child a copy, never the other way. So ./setup.sh can never set a variable back in your host shell, even if it exports. The export only reaches that child’s children, and the child dies on exit anyway. source skips the child entirely and runs everything in the host, which is why it sticks.

TL;DR Link to heading

./script.sh runs in a child shell, so its variables die when it exits. source script.sh runs in your current shell, so they stay. Plain NAME=x is local to your shell, export NAME=x also hands a copy to child processes. And that copy is one way only, downward. Children can never write back to you.