Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Here is the scenario:

> data/
> stuff/
> scripts/ -> /.../generic/scripts

When calling foo/scripts/bar.sh from any location (other folder or inside the folder), I would like to be able for the script to use foo/data and foo/stuff

However, if I simply use foo_dir='dirname $0'/.. then it'll go down the directory pointed by the symbolic link! (And obtain generic instead of foo!)

If I use some pattern to eat up the end of $0, then it's not working either since you could also call the script from inside the directory.

So, in the end, the only thing I have found is this extremely ugly stuff:

dir=`dirname $0`
cd $dir
dir=`pwd | sed -r 's_/[^/]+$__'`
cd -

... but I'm sure there is a better way, isn't there?

What I used:

dir=$( dirname $( cd `dirname $0`; pwd ) )

I don't know if it is perfect but it seems to behave as expected.

share|improve this question
This is indeed a very popular question on SO. And I think you should remove the part about "usual" and/or "nuts". – phunehehe Jun 6 '11 at 13:48
The link you provided doesn't address my specific issue. Knowing the current path is not a problem ...it's avoiding having .. point back to the wrong place! And avoiding having to resort to regexes to solve such a simple issue. – dagnelies Jun 6 '11 at 16:13
It's certainly unusual to have Linux driving your nuts. The drivers are still in the -mm tree. Maybe he's running rawhide? – Tom Anderson Jun 6 '11 at 17:42
up vote 1 down vote accepted

Sorry not reading your question more carefully the first time.

Instead of:

foo_dir = $(dirname $0)/..

how about:

foo_dir = $(dirname $(dirname $0))

However, this only works if there are at least one directory component in $0. Do you have that guarantee?

share|improve this answer
That does the trick. I can add always pwd to $0 if it is a relative path. – dagnelies Jun 7 '11 at 16:36

You can use the following one-liner:

DIR="$(cd "$( dirname "$0" )" && pwd )"
share|improve this answer
well ...this is incomplete and doesn't solve my problem. Why is it upvoted that much? The issue is about avoiding following the wrong backlinks while avoiding the usage of regexp to find the correct path. – dagnelies Jun 6 '11 at 16:09

Yes, .. in the presence of symlinks is a real problem. Rob Pike wrote a whole paper about this problem.

I suggest you avoid .. by using dirname with an absolute pathname, thus:

case $0 in
  /*) where="$0" ;;
  *)  where="$(pwd)/$0" ;;
# postcondition: $where is an absolute pathname for script
# N.B $where = .../foo/scripts/command

base="$(dirname "$(dirname "$where")")"
# postcondition: $base = .../foo


I actually use the first idiom (recover an absolute pathname from a possibly relative one) that I have a script just for this in my ~/bin directory.

Once you are dealing in absolute pathnames, dirname behaves sensibly.

share|improve this answer
Would i be right in thinking that the paper you're thinking of is 'Lexical File Names in Plan 9 - or Getting Dot-Dot Right'? – Tom Anderson Jun 7 '11 at 16:22
@Tom, yes, thanks for the link. – Norman Ramsey Jun 11 '11 at 2:37
s/realname/realpath/? – Michael Mrozek Jan 17 '13 at 18:01

Okay, so what you want is for the script being accessed through the symlink to be able to use files relative to the symlink, right? And for that to work even if the path used is absolute, relative, relative from inside the same directory, relative from a subdirectory, etc?

This is indeed not simple. Surprisingly unsimple, in fact - many programming languages have an 'abspath' function which would solve this problem in a flash. However, i do not believe there is such a function in the standard shell script toolkit.

However, you can build one quite simply, with a sed one-liner:

abspath() {
    { [[ "$1" =~ ^/ ]] && echo "$1" || echo "$(pwd)/$1"; } | sed -r ':. s#(/|^)\./#\1#g; t .; :: s#[^/]{1,}/\.\./##; t :'
foo_dir=$(dirname $(abspath $0))

I believe that this is entirely general, and will work for paths containing any amount of ./ and ../.

It does use regular expressions a bit, though,

share|improve this answer

Your post is a little rambling and some of the statements seem to be contradictory, but if my understanding is correct, you have a $0 of the form foo/scripts/bar.sh, and you're trying to extract the foo part. You furthermore guarantee that there will always be at least two levels of directories in that path (if you only have bar.sh, you're toast). Then you can just strip the last two components of the path:


You could call your script through scripts/bar.sh, though, in which case the simple pattern replacement wouldn't work. Not to mention calling the script via $PATH. You can make it conditional:

case $0 in
  */*/*) DIR=${0%/*/*};;
  ?*/*) DIR=.;;
  /*) echo 1>&2 "Storing $0 in the root directory is not supported, aborting."
      exit 125;;
  *) # The script was called through the `PATH`
    IFS=:; set -f; unset DIR
    for d in $PATH; do
      if [ -x "$d/$0" ]; then
        # If you put relative directories in your PATH, you get what you deserve.
    if [ -z "$DIR" ]; then
      echo 1>&2 "$0: Fatal error: I can't find myself."
      exit 125
    unset IFS; set +f;;
share|improve this answer

Just call out to Perl:

abspath=$(perl -MCwd -MFile::Basename -e 'print abs_path(dirname($ARGV[0]))' $0)

Note that abs_path(dirname(...)) may not be the same as dirname(abs_path(...)).

(Edit: Added -MFile::Basename)

share|improve this answer
Result: Undefined subroutine &main::dirname called at -e line 1.. Did I miss something? – dagnelies Jun 7 '11 at 15:10
The result of abs_path("foo/scripts/hello.sh") is "/.../generic/scripts/hello.sh" ...exactly the opposite of the desired behavior. – dagnelies Jun 7 '11 at 15:28

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.