| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | bash 5.2.26-6.el10 |
| Applies to | Linux distributions with Bash installed |
| Privilege | Normal user; examples create files in the current directory |
| Scope | source and . in the current shell, comparison with bash file and ./file, variables, functions, working directory, shell options, sourced arguments, exit status and return, PATH/sourcepath lookup, function-library guard pattern, security of sourcing, and Bash 5.3+ source -p path. Does not cover full startup-file order, first-script basics, or a complete functions tutorial. |
| Related guides | Bash shell script tutorial Shell scripting course hub |
Use source (or .) when a file should change the shell you are typing in. Run bash file or ./file when you want a separate process that cannot leave variables, functions, or cd changes behind.
That distinction matters for shared libraries, environment snippets, and one-off setup scripts. The sections below run the same helper file four ways and print what survived in the current shell afterward.
source vs Execute — Quick Decision Table
| Goal | Use |
|---|---|
| Load functions or variables into the current session | source ./lib.sh or . ./lib.sh |
| Run a standalone program that exits when done | bash ./script.sh or ./script.sh |
| Change only a subshell environment | ( source ./lib.sh; commands ) — explicit subshell compound |
| Read key/value data without executing arbitrary shell code | grep, awk, or a parser — not source |
source evaluates commands in the current shell execution environment. bash file and ./file start a new process (a child of your shell). ( commands ) is Bash's explicit subshell compound command — different from "every script is a subshell."
Pipelines such as cmd | while read run the loop in a subshell in Bash's default model — that is a pipeline rule, not the same thing as ./script.sh. See read a file line by line for the pipe-versus-redirection distinction.
| Command | Executable bit required? | Shebang used? |
|---|---|---|
source ./script.sh |
No | No |
. ./script.sh |
No | No |
bash script.sh |
No | No |
./script.sh |
Yes | Yes |
source and . make the current shell read the file, so the file does not need execute permission and its shebang does not choose the interpreter. bash script.sh explicitly chooses Bash, so it also does not require the executable bit. With ./script.sh, the operating system executes the file, so execute permission and a suitable shebang normally matter.
What source Does
The source builtin reads a file and runs each command as if you had typed it in the current shell. Assignments, function definitions, cd, set options, and trap handlers can all persist after the file finishes.
The exit status of source is the status of the last command executed from that file. If the file cannot be read, source fails.
source and . Syntax
In Bash these forms are equivalent:
source ./library.shThe POSIX dot form runs the same builtin:
. ./library.shPOSIX specifies the . command. In Bash, source is a builtin synonym for . — not a shell alias. Prefer ./filename when the file is in the current directory; bare names follow the lookup rules in the section below.
Run the Same File Four Ways
Work in a dedicated directory so PWD in the samples stays predictable:
mkdir -p /tmp/source-demoRun the rest of the comparisons from that directory:
cd /tmp/source-demoCreate a small library file that sets a variable, defines a function, and changes directory:
cat > library.sh <<'EOF'
#!/usr/bin/env bash
LIB_VAR="from_library"
lib_fn() { printf 'lib_fn:%s\n' "$1"; }
cd /tmp
EOFMake the library executable so ./library.sh can run as a separate program — only direct exec needs the execute bit; see Linux file permissions for chmod +x:
chmod +x library.shAdd a helper that prints what the current shell still sees:
show_state() {
printf 'LIB_VAR=%s\n' "${LIB_VAR-unset}"
if declare -F lib_fn >/dev/null 2>&1; then
printf 'lib_fn=defined\n'
else
printf 'lib_fn=unset\n'
fi
printf 'PWD=%s\n' "$PWD"
}Run the file as an executable in a child process:
./library.shInspect what the current shell still sees after the child process exits:
show_stateLIB_VAR=unset
lib_fn=unset
PWD=/tmp/source-demoOnly the child entered /tmp; your interactive shell stayed in /tmp/source-demo.
Start a new Bash process explicitly:
bash library.shThe parent shell should look the same as after ./library.sh:
show_stateLIB_VAR=unset
lib_fn=unset
PWD=/tmp/source-demoSame outcome: the child process discarded the assignments, function, and cd.
Load the file into the current shell with source:
source ./library.shSourcing runs cd /tmp in the current shell, so show_state should report the loaded variable and function:
show_stateLIB_VAR=from_library
lib_fn=defined
PWD=/tmpThe dot command produces the same result:
Reset with cd /tmp/source-demo, then load again with the dot command:
cd /tmp/source-demoLoad the library with the dot builtin:
. ./library.shThe dot form should match source line for line:
show_stateLIB_VAR=from_library
lib_fn=defined
PWD=/tmpOnly sourcing changed the session you are in. Executing the file as a program did not.
What Persists in the Current Shell?
After source, these commonly persist:
- Variables — ordinary assignments and
exportnames; see Bash variables for scope and export rules - Functions — definitions from Bash functions using
name() { ... }orfunction name { ... } - Working directory —
cdinside the sourced file - Shell options and traps —
set -e,set -o pipefail,shopt, andtrapaffect the current shell until you change them again
A child script process cannot leave those behind when it exits.
Reload .bashrc in the current shell
Many guides tell you to run:
source ~/.bashrcEquivalent Bash form:
. ~/.bashrcThis applies changes from .bashrc to the terminal you are already using — bashrc vs bash profile explains when that file runs versus .bash_profile. Running bash ~/.bashrc would start another Bash process instead, so environment changes made there would not remain in the current shell after that process exits.
Pass Arguments to a Sourced File
When you pass arguments to source, Bash uses them as the sourced file's positional parameters for that invocation, then restores the caller's positional parameters when the sourced file returns.
Create a short file:
cat > greet.sh <<'EOF'
printf 'Inside greet: $1=%s $2=%s $#=%s\n' "$1" "$2" "$#"
EOFSet a marker in the parent shell and source with two arguments:
set -- keep_mePrint the parent positional parameters before sourcing:
printf 'Before: $1=%s $#=%s\n' "$1" "$#"Pass Alice and Bob as temporary positional parameters to the sourced file:
source ./greet.sh Alice BobBefore: $1=keep_me $#=1
Inside greet: $1=Alice $2=Bob $#=2Check that the parent parameters returned:
printf 'After: $1=%s $#=%s\n' "$1" "$#"After: $1=keep_me $#=1The same rules apply to . ./greet.sh Alice Bob. See Bash script arguments for positional parameters in executed scripts.
Exit Status and return in Sourced Files
The status of source matches the last command in the file:
cat > status.sh <<'EOF'
false
true
EOFThe last command in the file is true, so source should succeed:
source ./status.shRead the status immediately — it reflects the last command in status.sh:
printf 'exit=%s\n' "$?"exit=0return inside a sourced file exits the source call with that status (it does not exit your interactive shell):
cat > ret.sh <<'EOF'
return 42
EOFreturn at the top level of a sourced file sets the status of the source call:
source ./ret.shThe return value becomes the exit status of source:
printf 'exit=%s\n' "$?"exit=42return outside a function is only valid while a file is being sourced (or inside a function). In an executed script, top-level return is an error. See Bash exit codes for how $? fits the wider model.
Source a Function Library
Put reusable functions in lib.sh and guard top-level side effects with Bash [ ] vs [[ ]] vs (( )) so the file is safe to source:
cat > lib.sh <<'EOF'
my_util() { printf 'util\n'; }
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
printf 'main block ran\n'
fi
EOFSourcing loads the function and skips the standalone block:
source ./lib.shCall the function that the library defined:
my_utilutilExecuting the same file runs the main block because BASH_SOURCE[0] and $0 match in that process:
bash lib.shmain block ranWhen the file is sourced, BASH_SOURCE[0] and $0 differ, so only the function definition is loaded. When the file is executed with bash lib.sh, they match and the standalone block runs. In a real program, the usual pattern is if [[ ${BASH_SOURCE[0]} == "$0" ]]; then main "$@"; fi.
How Bash Finds a Sourced File
Bash resolves the filename before it runs any commands from the file. The rules depend on whether the name contains / and on the sourcepath shell option (shopt -s sourcepath, on by default).
| Filename you pass | Lookup behavior |
|---|---|
Contains / (for example ./lib.sh or /etc/profile) |
Use that path directly |
No slash, sourcepath on (default) |
Search each directory in PATH; if still not found and Bash is not in POSIX mode, search the current directory |
No slash, sourcepath off |
Do not search PATH; resolve the bare name in the current directory only |
A bare name such as demo.sh is not the same as ./demo.sh. With sourcepath enabled, Bash may load a different demo.sh from PATH before it ever looks in the current directory. Prefer ./demo.sh or an absolute path in scripts you ship.
Demonstrate PATH lookup from /tmp/source-demo:
cd /tmp/source-demoCreate a helper script outside the current directory name:
mkdir -p binWrite a one-line script that only sets DEMO:
cat > bin/demo.sh <<'EOF'
DEMO=from_path
EOFPut bin on PATH and allow source to search it:
export PATH="$PWD/bin:$PATH"Enable sourcepath so bare filenames search PATH:
shopt -s sourcepathClear any old value, then source by bare filename:
unset DEMOBash should find demo.sh under $PWD/bin through PATH:
source demo.shConfirm the assignment reached the current shell:
printf 'DEMO=%s\n' "${DEMO-unset}"DEMO=from_pathPut a different demo.sh in the current directory so you can tell PATH lookup apart from the working-directory fallback:
printf 'DEMO=from_cwd\n' > demo.shTurn sourcepath off so bare names no longer search PATH:
shopt -u sourcepathClear the earlier value before sourcing again:
unset DEMOBash should load ./demo.sh from the current directory, not bin/demo.sh from PATH:
source demo.shConfirm the cwd file won:
printf 'DEMO=%s\n' "${DEMO-unset}"DEMO=from_cwdWith sourcepath disabled, PATH is not searched. A bare filename is resolved only in the current directory.
Bash 5.3+: source -p path
Bash 5.3 added a -p path option to source and .:
source -p path filename [arguments]Here path is a colon-separated directory list Bash uses to find filename. It replaces the usual PATH search for that one source call. Bash searches only the directories supplied with -p; it does not fall back to the current directory if the file is not found there. It does not change the shell's PATH variable, and commands inside the sourced file still use the existing PATH when they run external programs.
Prepare a library outside any PATH directory:
mkdir -p /tmp/source-libWrite a one-line library file there:
printf 'DEMO=from_custom_path\n' > /tmp/source-lib/demo.shOn Bash 5.3+, load it by search path instead of by full pathname:
source -p /tmp/source-lib demo.shRead the variable the sourced file set:
printf 'DEMO=%s\n' "$DEMO"DEMO=from_custom_path-p /tmp/source-lib tells Bash to look in /tmp/source-lib for demo.sh instead of searching $PATH. Your interactive PATH is unchanged.
Check the version before relying on -p:
if (( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3) )); then
printf 'source -p is available\n'
else
printf 'source -p needs Bash 5.3+\n'
fisource -p needs Bash 5.3+On Bash 5.2 (including the lab package bash 5.2.26-6.el10), -p is rejected:
source -p /tmp/source-lib demo.shbash: source: -p: invalid option
source: usage: source filename [arguments]Use an explicit path such as source /tmp/source-lib/demo.sh on older Bash, or gate on BASH_VERSINFO before calling source -p.
Why Sourcing “Config Files” Can Be Dangerous
source does not parse a file — it executes it as shell code. A file named app.conf can run rm, redefine functions, or exfiltrate data if you source it blindly.
| Safe-ish pattern | Risky pattern |
|---|---|
| Source known files you wrote under version control | source a downloaded “config” from the internet |
Read KEY=value lines with grep/awk when you only need data |
source untrusted dotfiles to “load variables” |
| Review third-party snippets before sourcing | Piping remote content into source |
Treat source like running a script in your current shell with your credentials and environment.
Common Problems
| Symptom | Likely cause | Fix |
|---|---|---|
No such file or directory |
Wrong path or missing ./ |
Use source ./file.sh or an absolute path |
| Variable set in script but empty in shell | You ran ./script.sh instead of sourcing |
Use source ./script.sh when you need persistence |
Unexpected cd after sourcing |
Sourced file changed directory | Audit the library; use explicit paths or a subshell |
return: can only be used in a function |
Top-level return in an executed script |
Use exit in executed scripts; return only when sourced or inside functions |
source: -p: invalid option |
Bash older than 5.3 | Use a full path, or gate on BASH_VERSINFO before source -p |
| Wrong file sourced | Bare name matched a different PATH entry |
Use ./file.sh, disable sourcepath, or put the directory first on PATH |
Summary
The Bash source command (and the dot builtin) evaluate a file in the current shell, so variables, functions, cd, and shell options can persist after the file returns. Running bash file or ./file starts a separate process whose changes disappear when that process exits — that is not the same as Bash's ( subshell ) syntax, which is an explicit compound command for isolation.
Use source ./lib.sh when you deliberately want to load a library into the session you are using. Pass arguments after the filename; Bash restores your previous positional parameters when sourcing finishes. Check $? after source for the last command's status, and use return N inside sourced files to exit the load with a chosen code.
Prefer explicit paths for files you ship, understand how sourcepath and bare filenames interact with PATH and the current directory, and never source untrusted files just to read configuration data. On Bash 5.3+, source -p path file searches path for file without changing your shell PATH; on Bash 5.2, use a full pathname or upgrade before relying on -p.
References
- GNU Bash Manual — Bourne Shell Builtins —
source - GNU Bash Manual — Bourne Shell Builtins —
. - POSIX Shell Command Language — dot
- Bash 5.3 release notes —
source -p path
Frequently Asked Questions
Is source the same as .?
In Bash, yes for practical use: both run a file in the current shell. POSIX requires .; source is a Bash builtin documented as a synonym for ..
Does source run in a subshell?
No. source runs in the current shell. A new process starts only when you execute bash file or ./file (or another interpreter). ( commands ) is the explicit subshell syntax.
Can sourced files receive arguments?
Yes. Words after the filename become $1, $2, and so on while the file runs. Bash restores the caller's positional parameters when source returns.
Why does a variable persist after source?
Because assignments in a sourced file apply to the current shell environment. Executed scripts assign in a child process that exits and takes those values with it.
What is source -p?
In Bash 5.3 and later, source -p path file searches the colon-separated directories in path for file instead of using $PATH. It does not search the current directory as a fallback. It does not change the shell PATH variable. It is not available in Bash 5.2.

