Bash is one of the most widely used shell scripting languages, estimated to be available on over 90% of Linux systems. With over 15 million lines of Bash code in major projects like the Linux kernel and Git version control system, following best practices for Bash scripting is critical for manageability and reliability. One key aspect is determining the directory location of the executing Bash script programmatically from within the script itself.

Knowing the absolute path to the Bash script file provides several useful benefits:

Loading Configuration Files

Often a Bash script needs to load a configuration file located in the same directory as itself. By getting the script path, you can reliably reference other files relative to it:

config_file="$script_dir/config.conf" 
source "$config_file"

This keeps the script self-contained and configurable without hardcoded paths.

Executing Other Scripts in the Directory

Finding the script path also helps with executing supplementary scripts in the same location:

"$script_dir/setup.sh"
"$script_dir/utils.sh" # Functions/helpers

This improves code structure by encapsulating reusable logic.

Creating Output Files

You can also safely generate output files next to the script without hardcoding:

output_file="$script_dir/results.csv"
# Generate output

Which is useful for workflows like statistical analysis.

Overall, determining the script directory aids with writing clean, reliable scripts that work predictably across environments. But how do you actually get the path within Bash?

Using the $PWD Variable

The $PWD variable contains the current working directory in Bash. So a simple way is:

#!/bin/bash

echo "Script directory: $PWD"

If you run the script, it will print the directory you executed the script from.

However, $PWD does have a major limitation — it only reflects the current working directory. If the script is sourced or imported into another Bash shell, $PWD will show the directory of that shell rather than the script location.

Using dirname and readlink -f

A more robust approach is using the dirname and readlink commands. The $0 parameter contains the original path and filename used to invoke the script.

We can pass this to readlink -f to get the full resolved path, then use dirname to extract just the directory portion:

#!/bin/bash

script_dir="$(dirname "$(readlink -f "$0")")"
echo "Script directory: $script_dir"

Breaking this down:

  • readlink -f "$0" returns the full absolute path from $0
  • dirname strips the actual filename, leaving only the directory
  • Works regardless of how the script was executed

One aspect here is readlink resolving symbolic links to directories. If the executed script file is a symbolic link, $0 will be the path of the link itself while readlink -f returns the real file path.

This protects against issues like a developer having ~/scripts symlinked to /opt/apps/scripts. By using readlink -f the script will correctly show /opt/apps/scripts as its location rather than ~.

Putting It In a Function

We can also wrap this dirname + readlink technique in a function for easier reusability:

#!/bin/bash

get_script_dir() {
  local source="${BASH_SOURCE[0]}"

  while [[ -h "$source" ]]; do 
    # Resolve path if $source is symlink 
    local dir="$( cd -P "$( dirname "$source" )" && pwd )"  
    source="$(readlink "$source")"
    [[ $source != /* ]] && source="$dir/$source" 
  done

  # Base directory of script
  echo "$( cd -P "$( dirname "$source" )" && pwd )"  
}

script_dir="$(get_script_dir)"
echo "Script directory: $script_dir"

Here we resolve any symlinks fully to always return the true script directory rather than any intermediate links. This handles even relative symlinks within the same directory by appending the appropriate base path.

Using a function promotes reusability across scripts. We can source a common utils.sh file of helpers in our script dir and simply call get_script_dir anywhere we need it. Much easier than copying the dirname+readlink logic everywhere!

Comparing Approaches

To summarize the key differences in techniques:

Approach Handling Symlinks Works When Sourced Use Case
$PWD No No Limited situations
dirname + readlink -f Yes Yes Reliable for most scripts
Wrapped function Yes Yes Best code reusability

$PWD is convenient but should typically be avoided in favor of readlink -f for reliability. And wrapping robust logic in functions promotes modular code.

Example Directory Structure

As an example to demonstrate, our script at /usr/local/bin/myapp/run.sh may look like:

/usr/local 
  └── bin
        └── myapp
              ├── run.sh
              ├── config.yaml
              └── utils.sh

If run.sh used get_script_dir(), the output would be:

/usr/local/bin/myapp

We could then reference utils.sh and config.yaml using this known base directory reliably.

Even if run.sh is executed from elsewhere or symlinked, get_script_dir() will uniformly return /usr/local/bin/myapp as the script‘s location.

Conclusion

Finding the containing directory of a Bash script is very useful for loading resources correctly, generating output files safely, and overall improving script reliability.

While $PWD can work in limited cases, it is better practice to leverage dirname and readlink -f $0 to get the full path info regardless of symlinks or how the script executes. Wrapping this logic in a reusable function promotes modular script development. Both the Linux kernel and Git codebases actually use variations of this technique internally as well.

So being able to programmatically determine directory location allows creating predictable, self-contained Bash scripts that work consistently across diverse environments – a key principle of good Linux shell scripting.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *