directory
script
bash

How do I get the directory where a Bash script is located from within the script itself?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The standard Bash pattern for getting the directory of the currently running script is a one-liner that combines dirname, BASH_SOURCE[0], cd, and pwd.

bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

This gives you the absolute path to the directory containing the script, regardless of where the caller invoked it from. It matters because scripts that reference sibling files (configs, libraries, data) break when the caller's working directory differs from the script's location. This article explains each piece of the one-liner, covers the symlink edge case, and walks through practical usage patterns.

Breaking Down the One-Liner

Each layer of the command handles a specific problem.

bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ComponentWhat It DoesWhy It is Needed
${BASH_SOURCE[0]}Path to the current script as Bash knows itMore reliable than $0 for sourced scripts
dirname "..."Extracts the directory portion of the pathStrips the filename, leaving only the directory
cd "..."Changes to that directory in a subshellResolves relative paths like ./scripts
pwdPrints the absolute working directoryProduces a clean absolute path
$(...)Command substitutionCaptures the result into SCRIPT_DIR

The subshell created by $(...) means the cd does not affect the caller's working directory. After this line runs, the shell is still in whatever directory it was before.

Why BASH_SOURCE[0] Instead of $0

Most older examples use $0, and it works in simple cases. But $0 and BASH_SOURCE[0] differ in important situations.

bash
#!/usr/bin/env bash
echo "\$0 = $0"
echo "BASH_SOURCE[0] = ${BASH_SOURCE[0]}"
Invocation Method$0BASH_SOURCE[0]
./myscript.sh./myscript.sh./myscript.sh
bash myscript.shmyscript.shmyscript.sh
source myscript.shbash (or -bash)myscript.sh
Called from another script via sourceName of the outer scriptName of the sourced file

When a script is sourced (with source or .), $0 becomes the name of the calling shell or calling script, not the file being sourced. BASH_SOURCE[0] always refers to the file where the line of code lives. For a script that might be both executed and sourced, BASH_SOURCE[0] is the correct choice.

Why dirname Alone is Not Enough

dirname extracts the directory component of a path, but it does not resolve relative paths to absolute ones.

bash
# If invoked as ./scripts/deploy.sh
dirname "${BASH_SOURCE[0]}"
# Returns: ./scripts

The result ./scripts is relative to the caller's current directory. If any code later changes the working directory (with cd), that relative path becomes invalid. The cd ... && pwd wrapping converts it to an absolute path like /home/user/project/scripts.

bash
1# Without cd + pwd: fragile relative path
2SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
3cd /tmp
4source "$SCRIPT_DIR/helpers.sh"  # FAILS: ./scripts/helpers.sh does not exist from /tmp
5
6# With cd + pwd: stable absolute path
7SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8cd /tmp
9source "$SCRIPT_DIR/helpers.sh"  # WORKS: /home/user/project/scripts/helpers.sh

A Complete Working Example

bash
1#!/usr/bin/env bash
2set -euo pipefail
3
4SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
6# Reference sibling files using SCRIPT_DIR
7CONFIG_FILE="$SCRIPT_DIR/config.env"
8LIB_DIR="$SCRIPT_DIR/lib"
9DATA_DIR="$SCRIPT_DIR/../data"
10
11printf 'Script directory:  %s\n' "$SCRIPT_DIR"
12printf 'Config file:       %s\n' "$CONFIG_FILE"
13printf 'Library directory:  %s\n' "$LIB_DIR"
14printf 'Data directory:    %s\n' "$DATA_DIR"
15
16# Source a helper library
17if [[ -f "$LIB_DIR/helpers.sh" ]]; then
18    source "$LIB_DIR/helpers.sh"
19fi

Run this from any directory and it will always find its sibling files.

bash
1# All of these produce the same SCRIPT_DIR
2./deploy.sh
3bash /home/user/project/scripts/deploy.sh
4cd /tmp && bash /home/user/project/scripts/deploy.sh

The basic pattern resolves the path as Bash sees it. If the script itself is a symlink, BASH_SOURCE[0] contains the symlink path, not the target. To resolve through symlinks to the physical file location, you need a loop.

bash
1#!/usr/bin/env bash
2set -euo pipefail
3
4SOURCE="${BASH_SOURCE[0]}"
5
6# Resolve symlinks iteratively
7while [[ -L "$SOURCE" ]]; do
8    DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
9    SOURCE="$(readlink "$SOURCE")"
10    # If readlink returned a relative path, make it absolute
11    [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
12done
13
14SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"

The -P flag on cd tells it to resolve symlinks in the directory path itself (not just in the script file). The loop follows each symlink hop until it reaches a real file.

  • Scripts installed via package managers that symlink into /usr/local/bin
  • Development setups where dotfiles are managed by a symlink farm (stow, dotbot)
  • Container images that symlink configuration scripts from shared volumes

If you know your script is never symlinked, the basic one-liner is sufficient and easier to understand.

Some systems provide realpath or readlink -f, which resolve symlinks and produce absolute paths in a single command.

bash
1# GNU coreutils (Linux)
2SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
3
4# realpath (available on most Linux, macOS 13+)
5SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
CommandAvailabilityResolves SymlinksNotes
cd + dirname + pwdAll Bash systemsNo (unless -P and loop)Most portable
readlink -fGNU/LinuxYesNot available on older macOS
realpathGNU/Linux, macOS 13+YesMay need coreutils on older macOS
readlink (no -f)macOS, LinuxOne level onlyNeeds a loop for chained symlinks

For maximum portability, especially in scripts that run on both macOS and Linux, the cd + dirname + pwd pattern remains the safest choice.

Common Patterns Using SCRIPT_DIR

Loading Configuration

bash
1SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
2
3if [[ -f "$SCRIPT_DIR/.env" ]]; then
4    set -a
5    source "$SCRIPT_DIR/.env"
6    set +a
7fi

Sourcing Library Files

bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/logging.sh"
source "$SCRIPT_DIR/lib/database.sh"

Referencing Data Files

bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INPUT="$SCRIPT_DIR/data/input.csv"
OUTPUT="$SCRIPT_DIR/output/results.json"

Setting Up PATH

bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export PATH="$SCRIPT_DIR/bin:$PATH"

All of these patterns anchor file references to the script location rather than the caller's working directory, which is the entire point of determining the script directory in the first place.

POSIX sh Compatibility

BASH_SOURCE is a Bash-specific variable. If your script needs to run under plain POSIX sh (dash, ash, busybox sh), you must fall back to $0.

bash
#!/bin/sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

This works for directly executed scripts but breaks when the script is sourced. If POSIX sh compatibility and sourcing support are both required, there is no fully portable solution. In practice, most scripts that need BASH_SOURCE should declare #!/usr/bin/env bash and require Bash.

Common Pitfalls

Using $0 in a script that might be sourced. When a script is sourced, $0 points to the parent shell, not the script file. Use BASH_SOURCE[0] in Bash scripts.

Using dirname without cd + pwd. The result may be a relative path like ./scripts or ../tools, which becomes invalid if the working directory changes later in the script.

Assuming the basic pattern resolves symlinks. It does not. If the script is a symlink to another location, BASH_SOURCE[0] contains the symlink path. Add explicit symlink resolution only when needed.

Forgetting to quote the path. Directories with spaces (like /home/user/my project/scripts) break without proper quoting. Always use "${BASH_SOURCE[0]}" with double quotes.

Using BASH_SOURCE in POSIX sh. The variable does not exist in sh. The script will either fail or produce an empty string. Check your shebang line.

Using pwd without cd first. Running pwd alone gives the caller's working directory, not the script's directory. The cd step is essential.

Summary

  • The standard pattern is SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
  • BASH_SOURCE[0] is safer than $0 because it works correctly even when the script is sourced.
  • The cd + pwd step converts potentially relative paths into stable absolute paths.
  • Add symlink resolution (a readlink loop or readlink -f) only when the script might be invoked through a symbolic link.
  • Use SCRIPT_DIR to build paths to sibling files, configs, and libraries instead of relying on the caller's working directory.
  • For POSIX sh compatibility, fall back to $0, but accept the sourcing limitation.

Course illustration
Course illustration

All Rights Reserved.