In bash, what is the difference between loading a script with `source setup.sh` (or `. setup.sh`) and running it as `./setup.sh`, and why does only one of them change the variables in your current shell?
answer
- one process or two
- current shell versus a child
- copies flow down, never back up
- functions must survive the file
- dot and source are the same builtin
basics
~20 ssource (and its POSIX synonym .) reads the file and runs its commands in the shell you are already in, so variables, functions and cd persist. ./setup.sh starts a separate shell that gets a copy of the environment and discards everything when it exits.
solid answer
~50 s`source setup.sh` and `. setup.sh` are the same builtin: bash reads the file and executes each line **in the current shell process**, exactly as if you had typed it. So anything the file does to shell state — setting variables, defining functions, `cd`, `export`, `set -e`, `trap` — is still in effect afterwards. Running `./setup.sh` instead starts a *separate* shell that inherits a copy of the exported environment; its assignments live in that copy and vanish when it exits, which is why a script can never `cd` or `export` back into its parent. That is the whole reason shell libraries are sourced rather than executed: you want the function definitions to survive. It also means a sourced file needs no shebang and no executable bit — those matter only when the kernel has to launch it.
code
bash · 5 lines#!/usr/bin/env bash
# demo.sh — compare `bash demo.sh` with `source demo.sh`
GREETING=hello
cd /tmp
echo "pid=$$ pwd=$PWD GREETING=$GREETING"go deeper
Be ready to say plainly that source/. runs the file in your current shell while ./file runs it in a new one, and give the concrete consequence: only the sourced form leaves variables, functions and cd behind.
Explain the mechanism — a child process inherits a copy of the exported environment and nothing travels back — and note what else sourcing imports: shell options, traps, IFS, and an exit that would kill the caller.
Show the design judgment: libraries are sourced and must therefore be side-effect free, entry points are executed, and a file written for one is not automatically safe for the other. Mention sourcing a slash-qualified path so $PATH cannot be hijacked.
Own the packaging decision for shared tooling: which pieces the team ships as sourceable libraries that mutate a developer's shell versus self-contained executables, and the support cost of anything that requires users to remember to source it.
## Two different things called "running a script" Bash gives you two ways to make the commands in a file happen, and they differ in *which shell process* runs them. **Sourcing** — `source file` or the POSIX-spelled `. file` — is a shell builtin. Bash opens the file and reads its commands into the shell that is already running, executing them one at a time as though you had typed them at that point. There is no new process and no new shell. **Executing** — `./file`, `bash file`, or invoking it by name from `$PATH` — hands the work to a *different* shell process. That child receives a copy of the exported environment and the current working directory as its starting state, runs the file, and then goes away. ## Why the child cannot change you Environment inheritance flows one way only: a new process gets a copy of the exported variables of the process that started it. Nothing in the shell or the operating system copies changes back up when the child finishes. So this file: ```bash # setenv.sh export TOKEN=abc cd /var/log ``` leaves `$TOKEN` unset and your directory unchanged when you run `./setenv.sh`, and sets both when you run `. setenv.sh`. `export` is often misread as "publish upward"; it does the opposite — it marks a variable to be *handed down* to processes this shell starts. This is why a tool that must change your shell (a virtualenv activator, an SDK's `env.sh`, a `cd`-to-project helper) ships as something you source, and why its documentation is emphatic about it. If a program genuinely must be executed and you still want the directory change, the workaround is to have it *print* the value and let the caller apply it: `cd "$(pick-project)"`. ## What sourcing carries with it Sourcing is not just about variables. Everything in the sourced file lands in your shell state: - **Function definitions** — the point of a shell library. `source lib/log.sh` and `log_info` is now a function you can call. - **Aliases, `shopt`/`set` options** — a sourced file that runs `set -e` or `set -x` turns those on for the *caller*, which is a classic surprise when a config file quietly enables tracing. - **`trap` handlers**, `umask`, `IFS`, and the working directory. - **`exit`** — a sourced file that calls `exit` terminates *your* shell, because it is the shell running the code. A corollary: a file written to be executed is not automatically safe to source. It may end with `exit 0`, clobber `IFS`, or leave you in a different directory. ## Mechanics worth knowing - `source` and `.` are the same builtin; `.` is the POSIX spelling that also works in `dash` and `sh`, `source` is a bash/ksh/zsh convenience. Prefer `.` in `#!/bin/sh` scripts. - **No shebang, no execute bit needed.** The `#!` line and the `+x` permission only matter when the kernel must launch the file as a program. Sourcing just reads text, so the shebang is an ordinary comment. Conversely, a file with `#!/usr/bin/env python3` that you *source* will be interpreted as bash and produce nonsense. - **A bare filename is searched on `$PATH`.** `source config` with no slash makes bash look along `$PATH` before the current directory (controlled by `shopt sourcepath`, on by default). In a script, always source a path containing a slash — `source ./config` or an absolute path — so you cannot accidentally pull in a file someone else put on the path. - **Bash lets you pass arguments**: `source lib.sh --verbose` sets `$1` inside the sourced file for its duration. POSIX `.` does not take arguments, so this is a bashism. - **The exit status of `source`** is the status of the last command it ran, so `source ./lib.sh || exit 1` is a meaningful guard when the library might not be loadable. ## Choosing between them Source when the file's *purpose* is to leave something behind: function libraries, environment setup, shared constants. Execute when the file's purpose is to *do* something and report success: a deploy step, a backup job, anything you would put in cron. Libraries meant for sourcing conventionally define functions and set variables and nothing else — no top-level side effects, no `exit`, no `set -e` — precisely because their statements run inside someone else's shell.
- Does a file you intend to source need a shebang line and the executable bit?No. Both matter only when the kernel launches the file as a program. `source` just reads the text and runs it with the current shell, so the `#!` line is an ordinary comment and permissions beyond read access are irrelevant. Libraries are conventionally shipped without `+x` precisely to signal "source me, don't run me".
- You want a script to leave the user in a different directory when it finishes. What are your options?An executed script cannot do it — the `cd` happens in its own process. Either ship it as a function in a sourced library, tell users to `source` it, or have it print the target directory and let the caller apply it: `cd "$(pick-project)"`. Wrapping the latter in a shell function is the friendliest packaging.
- What happens to `set -e` or a `trap` that a sourced file installs?They apply to the caller, because the caller is the shell executing those lines. A sourced config that runs `set -x` turns tracing on for the whole rest of your script or session, and a `trap ... EXIT` it installs replaces whatever handler the caller had. Libraries should not touch global shell options.
Sourcing is pasting the file's text into the shell you are already sitting in; executing it is handing the file to a temporary assistant in another room who does the work and then throws away everything on their desk.
saying these in an interview costs you the question
- Says an executed script can export a variable back to the parent shell
- Thinks source requires a shebang or the executable bit
- Claims `.` and `source` are different commands with different effects
- Believes a child shell's `cd` changes the parent's directory
- Reads `export` as publishing a variable upward rather than downward