12

Some of my coworkers prefer to write the following at the top of their BASH scripts to determine the directory containing the script:

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

Whereas I tend to prefer the following:

SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"

(Note that in most cases, we don't care about resolving symlinks, hence why we tend to use these rather than e.g. readlink)


Are there any merits of the cd ... && pwd approach over the dirname-only approach? It seems like it's just performing extra steps to achieve the exact same result, but I want to make sure there's not some nuance I'm missing.

0

2 Answers 2

17

The point of:

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

Which should rather be:

SCRIPT_DIR=$(
  CDPATH= cd -P -- "$(dirname -- "$BASH_SOURCE")" && pwd
)

Is that it gives you an absolute path (and with -P, the canonical absolute path, the realpath()), while $BASH_SOURCE ($var is short for ${var[0]} in bash like in ksh) may very well be a relative path such as ../file.bash.

Without -P, in the case of ../file.bash, cd .. may very well take you to a different directory from the one the script lays as by default (again like in ksh which introduced the (mis)feature) cd does a logical traversal where cd .. doesn't chdir("..") but rather chdir(dirname($PWD)) (and $PWD will have symlink components if you have cd'ed into paths with symlink components earlier).

We set CDPATH to the empty string (which as far as cd is concerned is equivalent to unsetting it), in case it's set to something else because for instance, there's a variable with that name in the environment, as otherwise a cd with a relative path (as in the BASH_SOURCE=dir/file.bash case) could take you some place else. It may be a good idea to unset CDPATH globally, but if that script is intended to be sourced, it may not be acceptable.

Note that it still doesn't work properly if the dirname of BASH_SOURCE or its realpath end in newline characters (as command substitution strips all the trailing newline characters, not just the one added by dirname/pwd for output), or if $BASH_SOURCE is -/file for instance (as cd - is short for cd -- "$OLDPWD").

Addressing the latter and more generally having a cd closer to the chdir() found in non-shell programming languages could be done with a helper function using the POSIX recommendation:

chdir() case $1 in
  (/*) cd -P "$1";;
  ('') echo>&2 'Directory is an empty string'; return 1;;
  ( *) CDPATH= cd -P "./$1";;
esac

Where prefixing relative paths with ./ avoids the need for -- and addresses the - issue as ./- contrary to - is not interpreted as the previous directory. See there for why the empty string is treated specially. That ./ prefix also disables CDPATH handling so that CDPATH= above is not needed other than as a reminder that that chdir helper disables CDPATH handling.

In zsh, you'd do just:

SCRIPT_DIR=$0:h:P

Or:

SCRIPT_DIR=$0:P:h

Where :P gives you the realpath() and :h the head (dirname, like in csh).

Both won't necessarily give you the same result if $0 happens to be a symlink itself.

Those don't have any of the issues of the bash approach above.

7
  • Is it the space after CDPATH= intentional? Commented Dec 20, 2024 at 17:58
  • 2
    @RomeoNinov yes, that is setting the variable CDPATH to the empty string since if that was set, the cd command could end up moving to an unexpected location. Commented Dec 20, 2024 at 17:59
  • @terdon, SCRIPT_DIR="$(dirname -- "${BASH_SOURCE[0]}")" (with the missing -- added) still potentially gives you a relative path, which may be undesirable as if the rest of the script happens to call cd/pushd/popd, that path will end up referring to a different directory afterwards. Commented Dec 20, 2024 at 18:10
  • Thank you for the insights @StéphaneChazelas! I realize actually from reading my scripts that I misspoke in my question lol, what I usually do is cd "$(dirname "${BASH_SOURCE[0]}")" at the top of my scripts so that I am changed into the directory containing the script. But that has led me to your good answer, so I'll take it! Commented Dec 20, 2024 at 18:30
  • 2
    @RonJohn, that's one way to look at it. Another way to look at it would be that those deserve medals as they help spot bugs in software that can't cope with arbitrary input because for instance they use the wrong or broken APIs. Same for those who use spaces, or *, ? in filenames which break sh scripts that forget to quote variables, or starting with - for those scripts that forget --. Or = for those scripts that use awk, or end in | for those scripts that user perl's <> (or the -n/-p options without sanitisation). Commented Dec 22, 2024 at 15:26
11

Frankly, I would argue both of those are needlessly complicated (and I would be wrong; see Stéphane's answer which explains that this is how to do it if you need to resolve to absolute paths). Even your simpler version is still calling an external command where none is needed. Why not just do:

script_dir="${BASH_SOURCE%/*}"

As for those two, I don't see any benefit in the even more convoluted approach using two external commands. Why run pwd if you can use $PWD, but why run so many commands in the first place? If you needed to resolve symlinks, then I could understand bringing in external commands, but for something as simple as this?

Now, there are edge cases where this fails. As explained by Stéphane Chazelas in a (now deleted) comment:

That gives you file.bash if $BASH_SOURCE is just file.bash (while dirname would give you .) and the empty string if $BASH_SOURCE is /file.bash (where dirname would give you /).

Whether or not this is relevant in your case is something only you can know.


As an aside, you really don't want to use CAPS for shell variable names. Since, by convention, global environment variables are capitalized, it is bad practice to also use caps for your own local variables because this can lead to naming collisions and hard to find bugs.

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.