How can a bash script know the directory it is installed in when it is sourced with . operator?

14,218

Solution 1

I believe $(dirname "$BASH_SOURCE") will do what you want, as long as the file you are sourcing is not a symlink.

If the file you are sourcing may be a symlink, you can do something like the following to get the true directory:

PRG="$BASH_SOURCE"
progname=`basename "$BASH_SOURCE"`

while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done

dir=$(dirname "$PRG")

Solution 2

Here is what might be an elegant solution:

script_path="${BASH_SOURCE[0]}"
script_dir="$(cd "$(dirname "${script_path}")" && pwd)"

This will not, however, work when sourcing links. In that case, one might do

script_path="$(readlink -f "$(readlink "${BASH_SOURCE[0]}")")"
script_dir="$(cd "$(dirname "${script_path}")" && pwd)"

Things to note:

  • arrays like ${array[x]} are not POSIX compliant - but then, the BASH_SOURCE array is only available in Bash, anyway
  • on macOS, the native BSD readlink does not support -f, so you might have to install GNU readlink using e.g. brew by brew install coreutils and replace readlink by greadlink
  • depending on your use case, you might want to use the -e or -m switches instead of -f plus possibly -n; see readlink man page for details

Solution 3

A different take on the problem - if you're using "." in order to set environment variables, another standard way to do this is to have your script echo variable setting commands, e.g.:

# settings.sh
echo export CLASSPATH=${CLASSPATH}:/foo/bar

then eval the output:

eval $(/path/to/settings.sh)

That's how packages like modules work. This way also makes it easy to support shells derived from sh (X=...; export X) and csh (setenv X ...)

Solution 4

We found $(dirname "$(realpath "$0")") to be the most reliable with both sh and bash. As team mates used them interchangeably, we ran into problems with $BASH_SOURCE which is not supported by sh.

Instead, we now rely on dirname, which can also be stacked to get parent, or grandparent folders.

The following example returns the parent dir of the folder that contains the .sh file:

parent_path=$(dirname "$(dirname "$(realpath "$0")")")
echo $parent_path
Share:
14,218
Gary
Author by

Gary

Updated on June 05, 2022

Comments

  • Gary
    Gary almost 2 years

    What I'd like to do is to include settings from a file into my current interactive bash shell like this:

    $ . /path/to/some/dir/.settings

    The problem is that the .settings script also needs to use the "." operator to include other files like this:

    . .extra_settings

    How do I reference the relative path for .extra_settings in the .settings file? These two files are always stored in the same directory, but the path to this directory will be different depending on where these files were installed.

    The operator always knows the /path/to/some/dir/ as shown above. How can the .settings file know the directory where it is installed? I would rather not have an install process that records the name of the installed directory.

  • Paul Tomblin
    Paul Tomblin over 15 years
    Right, that's how INN does it. "innconfvar" (I think that's what it's called) emits the configuration variables as sh, csh or perl commands.
  • Gary
    Gary over 15 years
    I like this idea. However, I'm not using it because the side effects would make the .settings file even more unreadable since it already has plenty of escapes to begin with. See below.
  • mpontillo
    mpontillo over 14 years
    Seems like it would also add complexity if you were sourcing in shell functions. (I was looking for a way to do this that works with both ksh and bash...)
  • Mark Longair
    Mark Longair almost 14 years
    To resolve symlinks you could just do: dir=$(dirname $(readlink -f "$BASH_SOURCE"))
  • asmeurer
    asmeurer about 10 years
    Why would the [0] be needed? @JasonDay doesn't have it in his answer.
  • Jacob Lee
    Jacob Lee about 10 years
    BASH_SOURCE is an array representing the call stack with the first element being the currently executing script.