How can a bash script know the directory it is installed in when it is sourced with . operator?
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, theBASH_SOURCE
array is only available in Bash, anyway - on macOS, the native BSD
readlink
does not support-f
, so you might have to install GNUreadlink
using e.g. brew bybrew install coreutils
and replacereadlink
bygreadlink
- 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
Gary
Updated on June 05, 2022Comments
-
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 over 15 yearsRight, 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 over 15 yearsI 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 over 14 yearsSeems 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 almost 14 yearsTo resolve symlinks you could just do:
dir=$(dirname $(readlink -f "$BASH_SOURCE"))
-
asmeurer about 10 yearsWhy would the
[0]
be needed? @JasonDay doesn't have it in his answer. -
Jacob Lee about 10 yearsBASH_SOURCE is an array representing the call stack with the first element being the currently executing script.