Get Bash Script Directory, Path and Name

Tested on RHEL 10.2 (Coughlan)
Package bash 5.2.26-6.el10
coreutils 9.5-8.el10_2
Applies to Linux distributions with Bash installed
Privilege Normal user
Scope Covers getting the script name, directory, and canonical path in Bash with $0, ${BASH_SOURCE[0]}, dirname, cd + pwd, and GNU readlink -f / realpath. Includes sourcing, symlinks, and loading a config file next to the script.
Related guides Shell scripting tutorial
How to write and run a Bash script
Bash shebang
Bash functions
Short on time?Beta Get the gist of this 9-minute read in seconds, then ask follow-up questions

Bash scripts often need to know their own name or directory so they can load configuration files, find libraries, or build paths independently of the current working directory.

$0, ${BASH_SOURCE[0]}, dirname, and GNU readlink -f or realpath answer different path questions. The examples below show how each behaves with relative paths, sourced files, and symlinks.


Quick Answer: Name vs Path vs Directory

Pick the variable or tool that matches what you actually need:

You need Use on GNU/Linux
How the script was invoked (relative or absolute) $0
The current Bash source file (works when sourced) ${BASH_SOURCE[0]}
Canonical absolute path (follows symlinks) readlink -f -- "${BASH_SOURCE[0]}" or realpath -- "${BASH_SOURCE[0]}"
Directory containing the script file cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd

For the most common task — an absolute script directory — use:

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

Use this when you need an absolute directory for the script as it was accessed. If the script itself may be a symlink and you need the target's directory instead, resolve it first with readlink -f or realpath.

Quote every expansion ("$0", "${BASH_SOURCE[0]}") so paths with spaces stay one argument. On GNU/Linux, readlink -f and realpath are common; they are not guaranteed on every Unix system.


$0: How the Script Was Invoked

$0 expands to the name Bash assigned to the shell or script when it started. When Bash executes a script file, $0 is normally the path used to identify that script, which may be relative or absolute. It does not necessarily identify the real file on disk, especially when sourcing or symlinks are involved.

It is useful for seeing how the current script invocation identifies itself, but it is not a reliable canonical file path.

The following examples use /tmp/bash-path-lab/bin/deploy.sh and run the same script in several ways. The script prints $0 and ${BASH_SOURCE[0]} side by side:

bash
cd /tmp/bash-path-lab && bash bin/deploy.sh
output
argv0=$0 => bin/deploy.sh
BASH_SOURCE[0]=bin/deploy.sh
basename=deploy.sh
dirname $0=bin
SCRIPT_DIR=/tmp/bash-path-lab/bin
readlink=/tmp/bash-path-lab/bin/deploy.sh

Both $0 and BASH_SOURCE[0] stay relative when you pass a relative path to bash.

Run the same script with ./ from the lab directory:

bash
cd /tmp/bash-path-lab && ./bin/deploy.sh
output
argv0=$0 => ./bin/deploy.sh
BASH_SOURCE[0]=./bin/deploy.sh
basename=deploy.sh
dirname $0=./bin
SCRIPT_DIR=/tmp/bash-path-lab/bin
readlink=/tmp/bash-path-lab/bin/deploy.sh

dirname "$0" is ./bin here, not an absolute directory — that is why you should not treat dirname "$0" alone as the script directory.

Pass an absolute path and $0 matches what you typed:

bash
cd /tmp && bash /tmp/bash-path-lab/bin/deploy.sh
output
argv0=$0 => /tmp/bash-path-lab/bin/deploy.sh
BASH_SOURCE[0]=/tmp/bash-path-lab/bin/deploy.sh
basename=deploy.sh
dirname $0=/tmp/bash-path-lab/bin
SCRIPT_DIR=/tmp/bash-path-lab/bin
readlink=/tmp/bash-path-lab/bin/deploy.sh

Use $0 when you only care about the invocation string (logging how a job was started). For file paths inside the script, prefer ${BASH_SOURCE[0]}.


${BASH_SOURCE[0]}: The Current Bash Source File

${BASH_SOURCE[0]} identifies the current Bash source file. Unlike $0, it continues to identify a file that was loaded with source, which makes it the better starting point for scripts and libraries that need their own location.

In a normal script execution it usually matches $0. Bash keeps a call stack in the BASH_SOURCE array — an array of source filenames associated with the current function call stack. Index 0 is the innermost file; ${BASH_SOURCE[1]} is the caller, and so on up the stack. For most “where is my script?” tasks, ${BASH_SOURCE[0]} is the right default.

When a library might be sourced, ${BASH_SOURCE[0]} is safer than $0 because sourcing does not update $0 to the library path — covered in the sourcing section below.


Get the Script Name

The script name is the last path component. Use basename on ${BASH_SOURCE[0]} so sourcing and relative launches still give the right file label:

bash
script_name=$(basename -- "${BASH_SOURCE[0]}")
echo "$script_name"
output
deploy.sh

The -- stops basename from treating a name that starts with - as a flag. In the examples above, basename returned deploy.sh whether $0 was bin/deploy.sh, ./bin/deploy.sh, or an absolute path.


Get the Script Directory Reliably

dirname strips the final path component. By itself, dirname "$0" or dirname "${BASH_SOURCE[0]}" can return . or ./bin when the script was started with a relative path.

A common Bash pattern converts the script's directory to an absolute path:

bash
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
echo "$script_dir"
output
/tmp/bash-path-lab/bin

cd -- avoids edge cases where a directory name starts with -. This converts a relative script directory such as ./bin into an absolute directory. It does not resolve a symlink when the script file itself is a symlink; in that case it returns the directory containing the link. Use readlink -f or realpath when you need the target file's canonical location. pwd can also differ from pwd -P when intermediate directories in the path are symlinks.


Get an Absolute/Canonical Script Path

On GNU/Linux, readlink -f canonicalizes a path by resolving . and .., repeated separators, and symbolic links, producing an absolute pathname:

bash
script_path=$(readlink -f -- "${BASH_SOURCE[0]}")
echo "$script_path"
output
/tmp/bash-path-lab/bin/deploy.sh

GNU realpath from coreutils does the same job on modern Linux:

bash
realpath -- "${BASH_SOURCE[0]}"
output
/tmp/bash-path-lab/bin/deploy.sh

Both commands follow the symlink chain to the final file. They are GNU/Linux conveniences — BSD readlink on macOS does not support -f, and POSIX does not require either command. Label them as GNU/Linux tools in portable scripts, or ship a small resolver function when you must support other platforms.

Combine with dirname when you need the directory of the resolved target:

bash
target_dir=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")

For how symlinks behave in the filesystem, see create a symbolic link on Linux.


What Changes When the File Is Sourced?

Running bash script.sh starts a new shell with $0 set to the script path. Running source script.sh (or . script.sh) executes the file in the current shell instead of starting a new Bash process. Sourcing does not replace $0; it keeps the current shell or parent script's $0. If you source a file from an interactive Bash shell, $0 is typically bash. If one script sources another, $0 remains the parent script's invocation path. ${BASH_SOURCE[0]} identifies the sourced file in either case.

In the interactive-shell case, this command sources a one-line helper:

bash
bash -c 'source /tmp/bash-path-lab/lib/helper.sh'
output
sourced: $0=bash BASH_SOURCE[0]=/tmp/bash-path-lab/lib/helper.sh

Any library that loads paths relative to itself should use ${BASH_SOURCE[0]}, not $0. In nested function calls, higher BASH_SOURCE indexes identify source files further up the call stack.


A symlink is a separate directory entry. Whether you want the link’s location or the target file’s location depends on what you are loading.

/tmp/bash-path-lab/lib/run-link points at bin/deploy.sh. Running the link shows the split:

bash
cd /tmp/bash-path-lab && /tmp/bash-path-lab/lib/run-link
output
argv0=$0 => /tmp/bash-path-lab/lib/run-link
BASH_SOURCE[0]=/tmp/bash-path-lab/lib/run-link
basename=run-link
dirname $0=/tmp/bash-path-lab/lib
SCRIPT_DIR=/tmp/bash-path-lab/lib
readlink=/tmp/bash-path-lab/bin/deploy.sh
Question $0 / ${BASH_SOURCE[0]} + dirname + cd + pwd readlink -f / realpath
Path printed for argv0 /tmp/bash-path-lab/lib/run-link N/A
Directory from cd + dirname /tmp/bash-path-lab/lib (where the link lives) N/A
Canonical script file path N/A /tmp/bash-path-lab/bin/deploy.sh (link target)

Put config files next to the symlink if users invoke the link. Put them next to the real script if you resolve with readlink -f first. Mixing the two layouts is a common source of “file not found” errors.


Practical Example: Load a Config Next to the Script

Many deployments keep etc/app.conf one level above bin/. The script should find that file no matter which directory the operator runs it from.

Create this layout:

text
bash-path-lab/
├── bin/
│   └── load-config.sh
└── etc/
    └── app.conf

This example intentionally uses the script's accessed location. If deployment uses symlinks but configuration belongs beside the real target, resolve ${BASH_SOURCE[0]} with readlink -f first.

load-config.sh resolves its own directory, walks up to etc/, and reads the config:

bash
#!/bin/bash
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
config="$(cd -- "$script_dir/.." && pwd)/etc/app.conf"

if [[ -f "$config" ]]; then
  echo "config=$config"
  cat "$config"
else
  echo "missing: $config" >&2
  exit 1
fi

Run it from /tmp so the current working directory is not the script folder:

bash
/tmp/bash-path-lab/bin/load-config.sh
output
config=/tmp/bash-path-lab/etc/app.conf
APP_NAME=demo

The script found app.conf without a hard-coded install prefix. The same pattern works for sourceing a shared library from lib/ next to bin/.


Common Broken Patterns

Pattern Why it breaks
script_dir=$(pwd)/$(dirname "$0") pwd is where you ran the command; dirname "$0" is where the script lives — concatenating them builds a path that often does not exist
dirname "$0" used alone as the install root Returns . or ./bin for relative invocations
$0 in a sourced library Sourcing does not change $0; it still identifies the current shell or parent script, not the sourced library
Assuming readlink -f exists everywhere GNU/Linux extension; not POSIX, not macOS BSD readlink
$_ as “the script path” $_ is the last argument of the previous command — unreliable for path logic

Stick to ${BASH_SOURCE[0]}, quote expansions, and use cd + pwd or GNU readlink -f / realpath on Linux when you need a canonical path.


Summary

Getting the Bash script name, path, and directory starts with choosing the right source: $0 for how the script was invoked, ${BASH_SOURCE[0]} for the file Bash is executing (including sourced libraries), and readlink -f or realpath on GNU/Linux when symlinks must be followed to the real file.

For the script name, basename -- "${BASH_SOURCE[0]}" strips the directory and handles relative launches. For the directory, cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd returns an absolute folder even when you started the script with ./bin/tool.sh. Symlinks split “directory of the link” from “path of the target” — pick one layout for config files and stay consistent.

The config-loader example shows a common use of script_dir: resolve the directory once, then build paths to configuration or library files from it. Avoid pwd/dirname concatenation and bare dirname "$0" when the script might be run from another working directory.


References


Frequently Asked Questions

What is the difference between $0 and BASH_SOURCE?

$0 identifies the current shell or top-level script invocation. Sourcing another file does not change it. ${BASH_SOURCE[0]} identifies the current source file, which is why it is preferable when a file may be sourced. They match when you execute a script normally, but diverge when the file is sourced or when you inspect the call stack with ${BASH_SOURCE[1]}, and so on.

No. readlink -f is a GNU extension common on Linux. BSD readlink on macOS does not support -f. GNU realpath is likewise a GNU coreutils tool. For Linux-only scripts they are fine; for portable scripts, document the requirement or use a resolver that does not assume GNU readlink.

On GNU/Linux, readlink -f -- "${BASH_SOURCE[0]}" or realpath -- "${BASH_SOURCE[0]}" prints the canonical path to the target file. ${BASH_SOURCE[0]} and dirname + cd + pwd without readlink keep the symlink’s own path and directory — use that when config files live beside the link.

Why does dirname "$0" return .?

dirname only removes the last path component. If you run ./tool.sh or tool.sh from the current directory, $0 has no directory prefix, so dirname returns . — meaning the current working directory, not the folder where the script file lives. Resolve with cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd instead.

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)