Bash source Command: Source vs Execute a Script

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
Short on time?Beta Get the gist of this 12-minute read in seconds, then ask follow-up questions

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:

bash
source ./library.sh

The POSIX dot form runs the same builtin:

bash
. ./library.sh

POSIX 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:

bash
mkdir -p /tmp/source-demo

Run the rest of the comparisons from that directory:

bash
cd /tmp/source-demo

Create a small library file that sets a variable, defines a function, and changes directory:

bash
cat > library.sh <<'EOF'
#!/usr/bin/env bash
LIB_VAR="from_library"
lib_fn() { printf 'lib_fn:%s\n' "$1"; }
cd /tmp
EOF

Make 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:

bash
chmod +x library.sh

Add a helper that prints what the current shell still sees:

bash
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:

bash
./library.sh

Inspect what the current shell still sees after the child process exits:

bash
show_state
output
LIB_VAR=unset
lib_fn=unset
PWD=/tmp/source-demo

Only the child entered /tmp; your interactive shell stayed in /tmp/source-demo.

Start a new Bash process explicitly:

bash
bash library.sh

The parent shell should look the same as after ./library.sh:

bash
show_state
output
LIB_VAR=unset
lib_fn=unset
PWD=/tmp/source-demo

Same outcome: the child process discarded the assignments, function, and cd.

Load the file into the current shell with source:

bash
source ./library.sh

Sourcing runs cd /tmp in the current shell, so show_state should report the loaded variable and function:

bash
show_state
output
LIB_VAR=from_library
lib_fn=defined
PWD=/tmp

The dot command produces the same result:

Reset with cd /tmp/source-demo, then load again with the dot command:

bash
cd /tmp/source-demo

Load the library with the dot builtin:

bash
. ./library.sh

The dot form should match source line for line:

bash
show_state
output
LIB_VAR=from_library
lib_fn=defined
PWD=/tmp

Only 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 export names; see Bash variables for scope and export rules
  • Functions — definitions from Bash functions using name() { ... } or function name { ... }
  • Working directory — cd inside the sourced file
  • Shell options and traps — set -e, set -o pipefail, shopt, and trap affect 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:

bash
source ~/.bashrc

Equivalent Bash form:

bash
. ~/.bashrc

This 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:

bash
cat > greet.sh <<'EOF'
printf 'Inside greet: $1=%s $2=%s $#=%s\n' "$1" "$2" "$#"
EOF

Set a marker in the parent shell and source with two arguments:

bash
set -- keep_me

Print the parent positional parameters before sourcing:

bash
printf 'Before: $1=%s $#=%s\n' "$1" "$#"

Pass Alice and Bob as temporary positional parameters to the sourced file:

bash
source ./greet.sh Alice Bob
output
Before: $1=keep_me $#=1
Inside greet: $1=Alice $2=Bob $#=2

Check that the parent parameters returned:

bash
printf 'After: $1=%s $#=%s\n' "$1" "$#"
output
After: $1=keep_me $#=1

The 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:

bash
cat > status.sh <<'EOF'
false
true
EOF

The last command in the file is true, so source should succeed:

bash
source ./status.sh

Read the status immediately — it reflects the last command in status.sh:

bash
printf 'exit=%s\n' "$?"
output
exit=0

return inside a sourced file exits the source call with that status (it does not exit your interactive shell):

bash
cat > ret.sh <<'EOF'
return 42
EOF

return at the top level of a sourced file sets the status of the source call:

bash
source ./ret.sh

The return value becomes the exit status of source:

bash
printf 'exit=%s\n' "$?"
output
exit=42

return 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:

bash
cat > lib.sh <<'EOF'
my_util() { printf 'util\n'; }

if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
  printf 'main block ran\n'
fi
EOF

Sourcing loads the function and skips the standalone block:

bash
source ./lib.sh

Call the function that the library defined:

bash
my_util
output
util

Executing the same file runs the main block because BASH_SOURCE[0] and $0 match in that process:

bash
bash lib.sh
output
main block ran

When 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:

bash
cd /tmp/source-demo

Create a helper script outside the current directory name:

bash
mkdir -p bin

Write a one-line script that only sets DEMO:

bash
cat > bin/demo.sh <<'EOF'
DEMO=from_path
EOF

Put bin on PATH and allow source to search it:

bash
export PATH="$PWD/bin:$PATH"

Enable sourcepath so bare filenames search PATH:

bash
shopt -s sourcepath

Clear any old value, then source by bare filename:

bash
unset DEMO

Bash should find demo.sh under $PWD/bin through PATH:

bash
source demo.sh

Confirm the assignment reached the current shell:

bash
printf 'DEMO=%s\n' "${DEMO-unset}"
output
DEMO=from_path

Put a different demo.sh in the current directory so you can tell PATH lookup apart from the working-directory fallback:

bash
printf 'DEMO=from_cwd\n' > demo.sh

Turn sourcepath off so bare names no longer search PATH:

bash
shopt -u sourcepath

Clear the earlier value before sourcing again:

bash
unset DEMO

Bash should load ./demo.sh from the current directory, not bin/demo.sh from PATH:

bash
source demo.sh

Confirm the cwd file won:

bash
printf 'DEMO=%s\n' "${DEMO-unset}"
output
DEMO=from_cwd

With 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 .:

text
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:

bash
mkdir -p /tmp/source-lib

Write a one-line library file there:

bash
printf 'DEMO=from_custom_path\n' > /tmp/source-lib/demo.sh

On Bash 5.3+, load it by search path instead of by full pathname:

bash
source -p /tmp/source-lib demo.sh

Read the variable the sourced file set:

bash
printf 'DEMO=%s\n' "$DEMO"
output
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:

bash
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'
fi
output
source -p needs Bash 5.3+

On Bash 5.2 (including the lab package bash 5.2.26-6.el10), -p is rejected:

bash
source -p /tmp/source-lib demo.sh
output
bash: 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


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.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)