5 min read

Making Portable Scripts: How to Reliably Find Your Script's Own Directory

Table of Contents

Ever written a script that works perfectly until you run it from a different directory? A common reason for this is when a script needs to access a related file (like a config file or another utility) but can’t find it. The script breaks because it’s looking for the file relative to your current working directory, not its own location.

Today, we’ll solve this classic problem once and for all. You’ll learn the most robust and reliable method to make your Bash scripts aware of their own location, making them truly portable.

The Problem: Unreliable Paths

Let’s imagine a simple project structure:

my_project/
├── configs/
│   └── app.conf
└── scripts/
    └── deploy.sh

Our deploy.sh script needs to read app.conf. A naive approach might be:

# deploy.sh - The WRONG way
#!/bin/bash

# This path is relative to where the user is, not where the script is!
CONFIG_PATH="../configs/app.conf"

echo "Attempting to read config from: $CONFIG_PATH"
cat "$CONFIG_PATH"

If we run this from the my_project directory, it works:

# In ~/my_project/
$ ./scripts/deploy.sh
# Attempting to read config from: ../configs/app.conf
# (contents of app.conf are displayed)

But what if we run it from our home directory?

# In ~/
$ ./my_project/scripts/deploy.sh
# Attempting to read config from: ../configs/app.conf
# cat: ../configs/app.conf: No such file or directory

The script fails because it’s looking for a configs directory in ~/../, which doesn’t exist. We need a way for the script to find its own directory, regardless of where we are.

The Solution: A Foolproof Snippet

The key to solving this lies in a special Bash variable and a couple of standard Linux commands. Here’s the step-by-step breakdown of the most reliable method.

Step 1: Use ${BASH_SOURCE[0]} to Find the Script’s Path

While many tutorials suggest using $0, it has a critical flaw: it doesn’t work correctly if the script is sourced (e.g., source my_script.sh).

The superior alternative is ${BASH_SOURCE[0]}. This variable from the BASH_SOURCE array always contains the path to the currently executing script file.

Let’s test it. Create a script named find_me.sh:

# find_me.sh
#!/bin/bash

echo "BASH_SOURCE[0] is: ${BASH_SOURCE[0]}"

Now, no matter how you run it, it correctly identifies its own path:

$ ./find_me.sh
# BASH_SOURCE[0] is: ./find_me.sh

$ /path/to/your/scripts/find_me.sh
# BASH_SOURCE[0] is: /path/to/your/scripts/find_me.sh

Step 2: Extract the Directory with dirname

Now that we have the script’s full path, we just need the directory portion. The dirname command is perfect for this. It strips the filename from a given path.

$ dirname /path/to/your/scripts/find_me.sh
# /path/to/your/scripts

$ dirname ./find_me.sh
# .

Step 3: Put It All Together for a Canonical Path

Combining these gives us the script’s directory. But to make it truly bulletproof, we need to handle relative paths (.) and symbolic links. We can do this by changing into the script’s directory and then printing the absolute path with pwd.

Here is the “magic” snippet. Place this at the top of your scripts:

#!/bin/bash

# Find the absolute path of the directory where the script is located.
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

echo "This script is located in: $SCRIPT_DIR"

Let’s break down this powerful one-liner:

  1. "${BASH_SOURCE[0]}": Gets the script’s path. Quoted to handle spaces.
  2. dirname -- "...": Extracts the directory part. The -- prevents issues if the path starts with a dash.
  3. cd -- "...": Changes the current directory to the script’s directory. This resolves any relative paths (. or ..) and follows symbolic links.
  4. &> /dev/null: Silences any output from the cd command.
  5. && pwd: If the cd command was successful, pwd prints the full, canonical path of that directory.
  6. SCRIPT_DIR=$(...): The $(...) syntax, called command substitution, captures the output of pwd and stores it in the SCRIPT_DIR variable.

A Practical, Portable Example

Let’s fix our deploy.sh script using this robust technique.

# my_project/scripts/deploy.sh - The RIGHT way
#!/bin/bash

# The reliable way to find the script's directory
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

# Construct the path to the config file relative to the script's location
CONFIG_FILE="$SCRIPT_DIR/../configs/app.conf"

echo "Script directory is: $SCRIPT_DIR"
echo "Looking for config file at: $CONFIG_FILE"

if [ -f "$CONFIG_FILE" ]; then
    echo "Success! Config file found and contains:"
    cat "$CONFIG_FILE"
else
    echo "Error: Config file not found!"
    exit 1
fi

Now, try running it from anywhere. It will always work.

# From your home directory
$ ~/my_project/scripts/deploy.sh
# Script directory is: /home/user/my_project/scripts
# Looking for config file at: /home/user/my_project/scripts/../configs/app.conf
# Success! Config file found and contains:
# (contents of app.conf)

# From the project root
$ ./scripts/deploy.sh
# Script directory is: /home/user/my_project/scripts
# Looking for config file at: /home/user/my_project/scripts/../configs/app.conf
# Success! Config file found and contains:
# (contents of app.conf)

Conclusion

Forgetting to account for a script’s location is a common pitfall, but the fix is simple and powerful. By placing this one-liner at the top of your scripts, you can reliably locate related files and create truly portable, professional-grade tools.

Your new go-to snippet:

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

Add this to your toolkit. It’s a best practice that will save you countless headaches and prevent those dreaded “it works on my machine” moments. Happy scripting!