skip to content

Sourcing and Shell Libraries

source runs a file in the current shell so its functions and variables persist, while executing it spawns a child that cannot change your environment. Anyone who has written a shared lib.sh knows the follow-ups: locating your own directory via BASH_SOURCE, and why sourcing a config file is really running it.

part ofBashoverview, primer and where to startread it →
on this pageshow

questions

5

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?

level: juniorimportance: must knowfreq 78%

answer

  1. one process or two
  2. current shell versus a child
  3. copies flow down, never back up
  4. functions must survive the file
  5. dot and source are the same builtin

basics

~20 s

source (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
bash
#!/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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

When a bash script needs to source a helper file that lives next to it, why is `${BASH_SOURCE[0]}` a better basis than `$0` for computing the file's own directory, and what does the usual `cd`-and-`pwd` wrapper add?

level: middleimportance: must knowfreq 55%

basics

~20 s

$0 is the name the outermost command was invoked with, so inside a sourced library it names the caller, not the library. ${BASH_SOURCE[0]} is always the file whose code is running. Wrapping it in cd and pwd turns a possibly relative path into an absolute one.

open as a page

A bash helper library defines `die() { echo "$1" >&2; exit 1; }`. A colleague sources the library into their interactive shell to try it out, calls `die`, and their terminal window closes. Explain what happened and how `return` behaves differently in a sourced file.

level: middleimportance: should knowfreq 42%

basics

~20 s

Sourced code runs in the caller's own shell, so exit terminates that shell — an interactive session closes, and a calling script stops mid-run. return only leaves the current function, or stops the sourcing early and hands a status back to source. Libraries should return, not exit.

open as a page

Your bash deploy script loads its settings with `source /etc/deploy.conf`. What does that actually do to the contents of that file, and what goes wrong when the file is writable by other users or is edited as if it were a plain .env file?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Sourcing a config file executes every line as bash in the running shell. Anyone who can write that file can run arbitrary commands with the script's privileges, and ordinary .env conventions such as unquoted values with spaces or inline comments do not mean what the author intended.

open as a page

In a bash project, two helper files each `source common.sh`, so common.sh is loaded twice in the same shell. What can go wrong, and what does an include guard look like in bash?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Bash has no import deduplication: every source re-executes the whole file. Re-running it can abort a strict script when readonly variables are reassigned, duplicate PATH entries, reset counters, replace traps and repeat expensive setup. A guard variable checked at the top with an early return prevents it.

open as a page