opscape

Shell builtins · bash builtin

source

Run the commands in a file inside the current shell, so its variables, functions and directory changes stay in effect.

source FILENAME [ARGUMENT...]    or    . FILENAME [ARGUMENT...]

9 of 9 examples run in Debian 13.7 on 2026-10-31 · manual: bash(1), SHELL BUILTIN COMMANDS

Running a script normally (./script.sh or bash script.sh) starts a new process. Whatever it sets, variables, functions, cd, set options, disappears when that process exits. source file instead reads the file and executes it in the current shell, as if you had typed its lines. That is how ~/.bashrc is loaded, how a script pulls in a library of functions, and how a virtualenv's activate changes your prompt.

. (a single dot) is the POSIX name for the same command; source is a bash spelling that dash does not have. If the file name contains no slash, both search PATH before the current directory, which is rarely what you want, so write ./file or a full path.

Sourcing has no isolation: a sourced file that calls exit exits your shell, and one that runs set -e changes it for the rest of your session. To stop early from a sourced file, use return. Only source files you trust, since they run with all of your shell's permissions and variables.

Examples

Load variables from a file into the current shell

app.env
APP_NAME=demo
APP_PORT=8080
source ./app.env
echo "$APP_NAME listens on $APP_PORT"
output
demo listens on 8080

The assignments in app.env run in the current shell, so the variables exist after the source line. Any file of NAME=value lines in shell syntax can be loaded this way.

Sourcing versus running a script

setdir.sh
cd /tmp
GREETING=hello
bash setdir.sh
echo "after bash:   pwd=$PWD greeting=[$GREETING]"
source ./setdir.sh
echo "after source: pwd=$PWD greeting=[$GREETING]"
output
after bash:   pwd=/home/dev/demo greeting=[]
after source: pwd=/tmp greeting=[hello]

bash setdir.sh runs in a child process; its cd and variable vanish with it. Sourced, the same lines change the current shell. This is why a script cannot cd for you unless you source it.

Use functions from a library file

source src/util.sh
log "starting backup"
type die | head -n 3
output
20:20:57 starting backup
die is a function
die () 
{ 

src/util.sh defines log and die. After sourcing it they are ordinary functions in this shell. log writes a timestamped line to stderr. Scripts usually locate the library relative to themselves with source "$(dirname "$0")/util.sh".

The portable form is a dot

. ./.env
echo "API_URL is $API_URL"
source ./.env
outputexit 127
API_URL is http://localhost:8080
/bin/sh: 3: source: not found

Under dash, /bin/sh on Debian and Ubuntu, . works and source is not a command. Use . in any script with a #!/bin/sh line.

A name without a slash is looked up in PATH

settings.sh
echo "loaded settings.sh from the current directory"
. settings.sh
echo "status: $?"
. ./settings.sh
output
loaded settings.sh from the current directory
status: 0
loaded settings.sh from the current directory

bash searches PATH first and then falls back to the current directory, so this works here, but if a settings.sh existed anywhere in PATH, that one would run instead. In dash and in bash's POSIX mode there is no fallback at all. Writing ./settings.sh avoids both surprises.

Export everything a file sets with set -a

set -a
source ./.env
set +a
env | grep -E '^(API_URL|DEBUG)='
output
API_URL=http://localhost:8080
DEBUG=false

Variables set by sourcing are shell variables, invisible to child processes. set -a marks every assignment for export while it is on, so env (a child process) sees both values from .env. A common way to load dotenv files.

Pass arguments to a sourced file

show-args.sh
echo "sourced with $# args: $*"
set -- outer1 outer2
source ./show-args.sh one two three
source ./show-args.sh
output
sourced with 3 args: one two three
sourced with 2 args: outer1 outer2

Extra words after the file name become $1, $2, ... while the file runs. Without them, the file sees the caller's positional parameters, here outer1 outer2.

Stop a sourced file early with return

guard.sh
if [ -z "${API_URL:-}" ]; then
  echo "guard: API_URL not set, skipping the rest"
  return 1
fi
echo "guard: using $API_URL"
source ./guard.sh; echo "status $?, shell still running"
API_URL=http://localhost:8080 source ./guard.sh
output
guard: API_URL not set, skipping the rest
status 1, shell still running
guard: using http://localhost:8080

return ends the sourced file and gives source its status, leaving the shell running. An exit in the same place would have ended the calling shell, or closed your terminal if you sourced it interactively.

Sourcing a missing file

source ./no-such-file.sh
echo "status: $?"
output
/bin/bash: line 1: ./no-such-file.sh: No such file or directory
status: 1

bash prints an error and source returns 1, but the shell continues. Check with [ -r file ] first when the file is optional, or let set -e stop a script that requires it.

Common pitfalls

  • Calling exit in a file meant to be sourced kills the caller's shell (or your terminal). Use return.

  • source file without a slash searches PATH first. Write source ./file or an absolute path.

  • source does not exist in dash, so #!/bin/sh scripts that use it fail with source: not found. Use ..

  • Sourcing a .env file written for Docker or a dotenv library: values with spaces or $ need shell quoting there, and an unquoted value with a space runs the rest as a command.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • . is POSIX and works in every Bourne-style shell; source exists in bash, zsh and ksh but not dash. On macOS /bin/sh is normally bash in POSIX mode, where source happens to work, so a script can pass there and fail on Debian.

Related