By Jiaaro

2008-09-12 20:39:56 8 Comments

How do I get the path of the directory in which a Bash script is located, inside that script?

I want to use a Bash script as a launcher for another application. I want to change the working directory to the one where the Bash script is located, so I can operate on the files in that directory, like so:

$ ./application


@dogbane 2008-10-29 08:36:45


DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

is a useful one-liner which will give you the full directory name of the script no matter where it is being called from.

It will work as long as the last component of the path used to find the script is not a symlink (directory links are OK). If you also want to resolve any links to the script itself, you need a multi-line solution:


while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

This last one will work with any combination of aliases, source, bash -c, symlinks, etc.

Beware: if you cd to a different directory before running this snippet, the result may be incorrect!

Also, watch out for $CDPATH gotchas, and stderr output side effects if the user has smartly overridden cd to redirect output to stderr instead (including escape sequences, such as when calling update_terminal_cwd >&2 on Mac). Adding >/dev/null 2>&1 at the end of your cd command will take care of both possibilities.

To understand how it works, try running this more verbose form:


while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
echo "DIR is '$DIR'"

And it will print something like:

SOURCE './' is a relative symlink to 'sym2/' (relative to '.')
SOURCE is './sym2/'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

@Jim Garrison 2011-08-09 16:07:27

This also does not work if the shell is invoked with an explicit bash command, as you would need to do if you wanted to invoke with a shell option, as in sh -x

@Dave Dopson 2011-08-16 18:03:08

For one last upgrade, try this: DIR="$( cd -P "$( dirname "$0" )" && pwd )" --- that will give you the absolute dereferenced path, ie, resolves all symlinks.

@eold 2011-08-18 22:00:00

It also does not work if you call the scripts via symbolic link; it returns the directory where the symlink is. But this may even be desirable.

@Dan Moulding 2011-10-19 15:54:43

You can fuse this approach with the answer by user25866 to arrive at a solution that works with source <script> and bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".

@beltorak 2011-12-15 09:47:26

let me try that again... The while loop breaks if any symlink in the chain is a relative symlink. Consider the following setup: /bin/bar -> /etc/alternatives/bar -> /usr/lib/bar-bsd -> bsdbar You'll need to change the while loop to while [ -h "$SOURCE" ] ; do LAST_SOURCE="$SOURCE"; SOURCE="$(readlink "$SOURCE")"; if [ "${SOURCE:0:1}" != / ] ; then SOURCE="$(dirname "$LAST_SOURCE")/$SOURCE"; fi; done

@rsaw 2012-03-08 18:54:04

When doing variable assignment to the output of command substitution or to the output of a variable reference, there is no reason whatsoever to use quotes. Proof: run x="my name is ryran" and then a=$x or a=${x/ryran/bob jones} or a=$(echo $x)

@fileoffset 2012-03-26 03:50:28

This is great, however I ran into a really weird problem. I have a funky command prompt defined, and when using this exact method I was getting unprintable characters in my DIR. I fixed it by using the following: DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

@Joseph Wright 2012-04-15 19:35:06

To deal with a relative symlink, I found I needed DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd -P "$( dirname "$SOURCE" )" && pwd )"

@dubiousjim 2012-05-31 17:21:15

@ryran: yes, but beware that you cannot portably expect local a=$x or export a=$x to work the same way. I'm not sure about a=$x foo, where foo is some command or builtin, but I suspect that you can't portably rely on the assignment behavior you describe there either.

@rsaw 2012-05-31 23:48:53

@dubiousjim: Hmm. What do you mean "portably expect"? I don't see how you'd have any trouble with any of it -- local/export, whatever. It all works in every version of BASH (and we're only talking about BASH here). As far as "a=$x foo where foo is some command or builtin" ... I'm not sure what you're trying to say there.

@dubiousjim 2012-06-01 00:15:18

@ryran: By "portably" I did mean in other shells. Granted this discussion is bash-specific, but it's worth being aware of what idioms one is using that may unexpectedly break in dash, ash, etc. It's easy to remember that [[ ]] is non-portable; more suprising that local a=$x with no quotes around the $x is too. I just checked FreeBSD sh (a species of ash): here local a=$x only assigns the first word of $x's expansion to a; similarly with export. But a=$x ./foo will (temporarily) assign the whole contents of $x's expansion to a.

@rsaw 2012-06-01 23:36:22

@dubiousjim: I hear you and you're right that it's good to be aware. I don't care though, because I don't ever code shell scripts in anything other than bash since virtually every linux distro comes with gnu bash installed. (I don't mean as the default shell; I just mean installed and available for bash scripts.)

@Qwertie 2012-06-20 21:59:29

Short version does not work in cygwin when the path is like "C:\...". Output of cd is : No such file or directoryabc where abc is the end of the directory name that contains the script. Second version works though.

@weynhamz 2012-12-25 12:13:16

Do not cd to other directory before this code, otherwise it will be wrong.

@JeffCharter 2013-01-11 23:12:38

Note that BASH_SOURCE was added for debugging purposes in version bash-3.0-alpha. So if you are working with a legacy system this won't work.

@user716468 2013-02-03 02:33:52

Sometimes cd prints something to STDOUT! E.g., if your $CDPATH has .. To cover this case, use DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"

@eonil 2013-04-03 09:41:13

Note that dirname $0 appriach won't work on just started bash shell session. (Of course this is not the case of OP) Try it on just launched terminal. In contrast, BASH_SOURCE approach works well.

@Chris Quenelle 2013-04-17 16:58:04

@fileoffset: You probably have CDPATH set to something. If that variable is set in your environment, the cd command will echo the new directory to stdout. Any portable ksh or bash script that uses cd should either redirect its output, or should prefix the cd command with CDPATH=.

@Chris Quenelle 2013-04-17 16:59:27

Is BASH_SOURCE[0] always the same as $0? If that is true, then why not use $0? It's simpler, more portable and more idiomatic. BASH_SOURCE is useful for processing as a list to find the call stack. In this case, $0 is good enough.

@Matt Kantor 2013-04-28 20:14:11

@Aaron Digulla 2013-11-04 13:40:40

@DaveDopson: I suggest to add >/dev/null after the cd because the above breaks in odd ways when cd prints something to stdout.

@kevinarpe 2013-11-24 07:19:27

In my experience, it is not necessary to add quotes around $() expressions. Thus: FILES=$(ls -1) and FILES="$(ls -1)" are exactly the same.

@Zitrax 2014-02-17 14:40:46

A stupid mistake, but at first I didn't get why ${BASH_SOURCE[0]} didn't work until I realized I had used #!/bin/sh instead of #!/bin/bash.

@x-yuri 2014-04-29 15:18:51

Why not use the following command? DIR=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")

@jpmc26 2014-08-14 15:13:30

According to ss64's docs, pwd takes a -P argument. The description states that the -P argument ensures the path contains no symbolic links. Could this argument be used to follow symbolic links instead of the loop?

@slm 2014-09-02 19:52:53

@x-yuri - FYI, readlink is not available in the current form on OSX, hence this method would be best, if you're trying to write a semi-crossplatform script on Linux & OSX using Bash.

@lpapp 2014-10-20 17:09:41

Sadly, this will not work from ~/.bashrc because the $DIR variable is only evaluated once at login time, not dynamically all the time. :-(

@jpmc26 2014-12-03 04:00:58

@lpapp If you're trying to reference the directory of ~/.bashrc, you already know it! It's ~. ;) Normally, I don't like to assume, but in this case, it's a pretty safe assumption since it's happening at log in and bash is only looking in that specific place.

@Pete 2015-05-06 12:02:02

This solution doesn't work for autoenv .env scripts (or similar scripts that execute when you cd because it will call itself recursively) why not just DIR=$( dirname "${BASH_SOURCE[0]}" )

@tvlooy 2015-06-09 19:32:10

This accepted answer is not ok, it doesn't work with symlinks and is overly complex. dirname $(readlink -f $0) is the right command. See for a testcase

@Adrian Günter 2015-10-28 23:38:56

@tvlooy IMO your answer isn't exactly OK as-is either, because it fails when there is a space in the path. In contrast to a newline character, this isn't unlikely or even uncommon. dirname "$(readlink -f "$0")" doesn't add complexity and is fair measure more robust for the minimal amount of trouble.

@gouessej 2015-10-29 10:39:54

This solution doesn't work in a script called when running an application by clicking on an icon under Ubuntu, the script is mentioned in the Exec attribute. The same script works flawlessly in the terminal. I'm currently investigating. I don't want to set the Path attribute in the desktop file as it wouldn't be distro-agnostic.

@Rany Albeg Wein 2016-01-16 14:02:54

As a side note: By convention, environment variables (PATH, EDITOR, SHELL, ...) and internal shell variables (BASH_VERSION, RANDOM, ...) are fully capitalized. All other variable names should be lowercase. Since variable names are case-sensitive, this convention avoids accidentally overriding environmental and internal variables.

@Rany Albeg Wein 2016-01-30 02:16:59

By convention, environment variables (PATH, EDITOR, SHELL, ...) and internal shell variables (BASH_VERSION, RANDOM, ...) are fully capitalized. All other variable names should be lowercase. Since variable names are case-sensitive, this convention avoids accidentally overriding environmental and internal variables.

@Jesin 2016-09-06 00:29:24

To deal with symlinks, use pwd -P

@smancill 2017-03-20 03:23:38

@switch87 your solution doesn't work in all cases. Please do not edit the answer if you are not 100% sure.

@Andreas 2017-04-27 14:27:16

The oneliner assumes 'cd' is silent. If not you will end up with garbage. Better: DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >>/dev/null && pwd )"

@Alexander Ljungberg 2017-05-09 16:14:06

@tvlooy your comment is not macOS (or probably BSD in general) compatible, while the accepted answer is. readlink -f $0 gives readlink: illegal option -- f.

@tvlooy 2017-05-09 20:31:53

All BSD's have it. OSX doesn't. Install coreutils and use greadline

@zored 2017-07-17 11:00:22

Cute format: DIR=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)

@tishma 2017-09-09 08:47:30

Seems like no one has the problem with changing working directory without putting it back where it was. :( EDIT: My bad $() creates a new shell, doesn't it?

@Novice C 2017-09-27 12:26:21

While both -h and -L opts for test find wide dual support, there is a preference to favor -L. See these stackoverflow questions: ref 1 and ref 2. However, your milage may vary depending on who is refusing to follow standards.

@Ethan Reesor 2017-11-08 05:13:54

So, @tvlooy, if I'm running on macOS, your solution is to install a software package so I can use your answer? That's far worse than the little bit of complexity of the accepted answer (assuming no symlinks).

@Vladimir Panteleev 2017-12-12 21:31:16

Why not use realpath (as in the other answers) instead of the while loop?

@muni764 2018-08-21 14:16:39

Probably it's not robust, for the link and no link aproach I do the following: LINK=readlink "${BASH_SOURCE[0]}" if [ $? -eq 0 ]; then echo "Link detected" export SCRIPT_DIR="$( cd "$( dirname "$LINK" )" && pwd )" else echo "No link" export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" fi

@Gabriel Staples 2018-12-02 05:41:52

Note: don't forget the #!/bin/bash at the top. #!/bin/sh won't work. You'll get a Bad substitution error.

@Rich Kuzsma 2019-05-09 15:13:53

The one-liner fails on OSX El Capitan, because of Bash Sessions, which pollute the output with Saving session... ...copying shared history... ...saving history...truncating history files... ...completed. This works: read DIR < <(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)

@wieczorek1990 2019-06-28 09:20:26

I always change the formatting of this line into: DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

@Alexander - Reinstate Monica 2019-07-12 21:27:23

Is it possible to wrap this in a function, or does it have to be copy/pasted all over the place?

@Andrei LED 2019-08-27 11:49:28

I just found out that the above does not work when the script is straight out executed rather than sourced:…

@Neil McGill 2019-09-24 13:04:54

@Andrei LED, this seems to work for the bash bug case: $(cd $(dirname ${BASH_SOURCE[@]}) &>/dev/null && pwd)

@Nam G VU 2019-11-22 15:57:51

why ${BASH_SOURCE[0]} instead of simpler $BASH_SOURCE

@Gabriel Staples 2020-02-10 19:49:58

For many cases, realpath will work in place of all this; ex: PATH_TO_SCRIPT=$(realpath $0). I found about realpath from here: I also just posted this as a new answer here:….

@Sergei 2020-04-06 12:14:44

dirname executes an extra process. consider ${BASH_SOURCE[0]%/*} instead.

@MAXdB 2020-07-03 02:29:15

tvlooy's compact solution worked for all the torture test cases that I tried. Dave Dopson's more complex solution also works for all the torture test cases that I tried.

@MAXdB 2020-07-03 02:40:49

Torture cases included running as a service and using multiple variations of differently named relative symlinks calling differently named relative symlinks finally calling the script relatively.I also need the actual name of the script, not the name of the first symlink. TRUESCRIPT="$(dirname $(readlink -f $0))/$(basename $(readlink -f $0))"

@todd_dsm 2020-06-15 20:35:48

I think the simplest answer is a parameter expansion of the original variable:

#!/usr/bin/env bash                                                                

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"            
echo "opt1; original answer: $DIR"                                                 
echo ''                                                                            

echo "opt2; simple answer  : ${BASH_SOURCE[0]%/*}"                              

Should produce output like:

$ /var/tmp/
opt1; original answer: /var/tmp

opt2; simple answer  : /var/tmp

The variable/parameter expansion ${BASH_SOURCE[0]%/*}" seems much easier to maintain.

@michaeljt 2015-10-09 08:16:09

(Note: this answer has been through many revisions as I improved on the original. As of the last revision, no one had commented or voted yet.)

I am adding this answer as much for my own benefit - to remember it and gather comments - as for anyone else's. The key part of the answer is that I am reducing the scope of the problem: I forbid indirect execution of the script via the path (as in /bin/sh [script path relative to path component]).

This can be detected because $0 will be a relative path which does not resolve to any file relative to the current folder. I believe that direct execution using the #! mechanism always results in an absolute $0, including when the script is found on the path.

I also require that the pathname and any pathnames along a chain of symbolic links only contain a reasonable subset of characters, notably not \n, >, * or ?. This is required for the parsing logic.

There are a few more implicit expectations which I will not go into (look at this answer), and I do not attempt to handle deliberate sabotage of $0 (so consider any security implications). I expect this to work on almost any Unix-like system with a Bourne-like /bin/sh.

Comments and suggestions welcome!

    while test -n "${path}"; do
        # Make sure we have at least one slash and no leading dash.
        expr "${path}" : / > /dev/null || path="./${path}"
        # Filter out bad characters in the path name.
        expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
        # Catch embedded new-lines and non-existing (or path-relative) files.
        # $0 should always be absolute when scripts are invoked through "#!".
        test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
        # Change to the folder containing the file to resolve relative links.
        folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
        path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
        cd "${folder}"
        # If the last path was not a link then we are in the target folder.
        test -n "${path}" || pwd

@Matt Tardiff 2008-10-14 16:39:03

This works in bash-3.2:

path="$( dirname "$( which "$0" )" )"

If you have a ~/bin directory in your $PATH, you have A inside this directory. It sources the script ~/bin/lib/B. You know where the included script is relative to the original one, in the lib subdirectory, but not where it is relative to the user's current directory.

This is solved by the following (inside A):

source "$( dirname "$( which "$0" )" )/lib/B"

It doesn't matter where the user is or how he calls the script, this will always work.

@Reinstate Monica Please 2014-01-13 22:30:04

The point on which is very debatable. type, hash, and other builtins do the same thing better in bash. which is kindof more portable, though it really isn't the same which used in other shells like tcsh, that has it as a builtin.

@Charles Duffy 2014-06-09 03:42:39

"Always"? Not at all. which being an external tool, you have no reason to believe it behaves identically to the parent shell.

@User8461 2018-03-09 12:56:45

These are short ways to get script information:

Folders and files:

    Script: "/tmp/src dir/"
    Calling folder: "/tmp/src dir/other"

Using these commands:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo Calling-Dir : `pwd`

And I got this output:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name :
     Script-Name :

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

Also see:

@User8461 2020-05-12 19:30:29

I think my answer is ok because it is hard to find a simple working edition. Here you can take the code you like e.g. cd + pwd, dirname + realpath or dirname + readlink. I am not sure that all parts exist before and most answers are complex and overloaded. Here you can pike out the code you like to use. At least please do not remove it as I need in the future :D

@Thamme Gowda 2018-11-07 04:30:59

I am tired of coming to this page over and over to copy paste the one-liner in the accepted answer. The problem with that is it is not easy to understand and remember.

Here is an easy-to-remember script:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be

@agc 2019-04-04 11:55:34

Or, more obscurely, on one line: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")

@User9123 2020-03-25 01:14:16

Why isn't this the accepted answer? Is there any difference using realpath from resolving "manually" with a loop of readlink? Even the readlink man page says Note realpath(1) is the preferred command to use for canonicalization functionality.

@User9123 2020-03-25 01:32:10

And by the way shouldn't we apply realpath before dirname, not after? If the script file itself is a symlink... It would give something like DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Actually very close to the answer proposed by Simon.

@Wang 2020-05-24 12:23:58

@User9123 I think the accept one is try to be compatible with all popular shell/distro. More over, depending on what you are trying to do, in most cases people want to obtain the directory where the symlink located instead of the directory of the actual source.

@puchu 2020-08-29 10:15:02

The only reason is missing coreutils on mac. I am using SCRIPT=$(realpath "${BASH_SOURCE[0]}") + DIR=$(dirname "$SCRIPT").

@Simon Rigét 2016-02-12 23:40:21

This should do it:

DIR="$(dirname "$(readlink -f "$0")")"

This works with symlinks and spaces in path.

See the man pages for dirname and readlink.

From the comment track it seems not to work with Mac OS. I have no idea why that is. Any suggestions?

@Bruno Negrão Zica 2016-06-14 18:27:08

with your solution, invoking the script like ./ shows . instead of the full directory path

@Denis The Menace 2016-11-21 09:55:33

There's no -f option for readlink on MacOS. Use stat instead. But still, it shows . if you are in 'this' dir.

@dragon788 2019-04-22 15:59:00

You need to install coreutils from Homebrew and use greadlink to get the -f option on MacOS because it is *BSD under the covers and not Linux.

@hagello 2020-03-17 11:22:08

You should add double quotes surrounding all the right hand side: DIR="$(dirname "$(readlink -f "$0")")"

@Gabriel Staples 2020-02-10 19:46:22

For many cases, all you need to acquire is the full path to the script you just called. This can be easily accomplished as follows. Note that realpath is part of GNU coreutils. If you don't have it already installed (it comes default on Ubuntu), you can install it with sudo apt update && sudo apt install coreutils.


PATH_TO_SCRIPT="$(realpath $0)"

Example output:

$ ./
PATH_TO_SCRIPT = "/home/gabriel/dev/linux_scripts/practice/realpath/"

Note that realpath also successfully walks down symbolic links to determine and point to their targets rather than pointing to the symbolic link.


  1. How to retrieve absolute path given relative

@Muhammad Adeel 2020-02-24 17:58:05

Keep it simple.

#!/usr/bin/env bash
echo $sourceDir

@Geoff Nixon 2013-12-21 17:47:37

I believe I've got this one. I'm late to the party, but I think some will appreciate it being here if they come across this thread. The comments should explain:

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.


for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "[email protected]"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "[email protected]")"; link="$(readlink "$(basename "[email protected]")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "[email protected]" ]; then if $(ls -d "[email protected]" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "[email protected]" -a "$link" = "[email protected]" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "[email protected]" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "[email protected]" | cut -c1)" = '/' ]; then
   printf "[email protected]\n"; exit 0; else printf "$(pwd)/$(basename "[email protected]")\n"; fi; exit 0

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
 printf "$(pwd)/$(basename "$newlink")\n"

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "[email protected]"

@user1338062 2014-05-28 07:16:09

For systems having GNU coreutils readlink (eg. linux):

$(readlink -f "$(dirname "$0")")

There's no need to use BASH_SOURCE when $0 contains the script filename.

@osirisgothra 2015-02-05 00:07:46

unless the script was sourced with . or 'source' in which case it will still be whatever script sourced it, or, if from the command line, '-bash' (tty login) or 'bash' (invoked via 'bash -l') or '/bin/bash' (invoked as an interactive non-login shell)

@user1338062 2018-05-13 04:35:07

I added second pair of quotes around dirname call. Needed if the directory path contains spaces.

@hurrymaplelad 2011-09-16 19:05:51

$_ is worth mentioning as an alternative to $0. If you're running a script from Bash, the accepted answer can be shortened to:

DIR="$( dirname "$_" )"

Note that this has to be the first statement in your script.

@clacke 2014-01-31 14:55:13

It breaks if you source or . the script. In those situations, $_ would contain the last parameter of the last command you ran before the .. $BASH_SOURCE works every time.

@Atul 2019-06-20 22:08:51

The shortest and most elegant way to do this is:

DIRECTORY=$(cd `dirname $0` && pwd)

This would work on all platforms and is super clean.

More details can be found in "Which directory is that bash script in?".

@ruuter 2019-09-26 08:43:04

great clean solution, but this will not work if the file is symlinked.

@Fuwjax 2010-04-13 22:12:31

This is a slight revision to the solution e-satis and 3bcdnlklvc04a pointed out in their answer:

pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    popd > /dev/null

This should still work in all the cases they listed.

This will prevent popd after a failed pushd, thanks to konsolebox.

@Jay Taylor 2010-06-23 20:32:50

This works perfectly to get the "real" dirname, rather than just the name of a symlink. Thank you!

@konsolebox 2014-07-03 04:15:43

Better SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }

@Fuwjax 2015-01-19 20:03:31

@konsolebox, what are you trying to defend against? I'm generally a fan of inlining logical conditionals, but what was the specific error that you were seeing in the pushd? I'd match rather find a way to handle it directly instead of returning an empty SCRIPT_DIR.

@konsolebox 2015-01-20 19:21:28

@Fuwjax Natural practice to avoid doing popd in cases (even when rare) where pushd fails. And in case pushd fails, what do you think should be the value of SCRIPT_DIR? The action may vary depending on what may seem logical or what one user could prefer but certainly, doing popd is wrong.

@Amit Naidu 2020-05-27 03:16:32

All those pushd popd dangers could be avoided simply by dropping them and using cd + pwd enclosed in a command substitution instead. SCRIPT_DIR=$(...)

@Mike Bethany 2010-09-09 07:19:22

I tried all of these and none worked. One was very close but had a tiny bug that broke it badly; they forgot to wrap the path in quotation marks.

Also a lot of people assume you're running the script from a shell so they forget when you open a new script it defaults to your home.

Try this directory on for size:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

This gets it right regardless how or where you run it:

echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

So to make it actually useful here's how to change to the directory of the running script:

cd "`dirname "$0"`"

@reinierpost 2014-03-28 13:12:03

Doesn't work if the script is being sourced from another script.

@hagello 2020-03-17 11:20:54

This does not work if the last part of $0 is a symbolic link pointing to an entry of another directory (ln -s ../bin64/foo /usr/bin/foo).

@Jim 2008-09-13 01:07:11

I don't think this is as easy as others have made it out to be. pwd doesn't work, as the current directory is not necessarily the directory with the script. $0 doesn't always have the information either. Consider the following three ways to invoke a script:




In the first and third ways $0 doesn't have the full path information. In the second and third, pwd does not work. The only way to get the directory in the third way would be to run through the path and find the file with the correct match. Basically the code would have to redo what the OS does.

One way to do what you are asking would be to just hardcode the data in the /usr/share directory, and reference it by its full path. Data shoudn't be in the /usr/bin directory anyway, so this is probably the thing to do.

@Richard Duerr 2015-11-18 18:54:12

If you intend to disprove his comment, PROVE that a script CAN access where it's stored with a code example.

@Mr Shark 2008-09-12 20:50:55

You can use $BASH_SOURCE:


scriptdir=`dirname "$BASH_SOURCE"`

Note that you need to use #!/bin/bash and not #!/bin/sh since it's a Bash extension.

@Till 2010-10-25 17:06:20

When I do ./foo/script, then $(dirname $BASH_SOURCE) is ./foo.

@purushothaman poovai 2019-12-17 05:17:43

@Till, In this case we can use realpath command to get full path of ./foo/script. So dirname $(realpath ./foo/script) will give the path of script.

@phatblat 2009-09-26 20:38:02

The dirname command is the most basic, simply parsing the path up to the filename off of the $0 (script name) variable:

dirname "$0"

But, as matt b pointed out, the path returned is different depending on how the script is called. pwd doesn't do the job because that only tells you what the current directory is, not what directory the script resides in. Additionally, if a symbolic link to a script is executed, you're going to get a (probably relative) path to where the link resides, not the actual script.

Some others have mentioned the readlink command, but at its simplest, you can use:

dirname "$(readlink -f "$0")"

readlink will resolve the script path to an absolute path from the root of the filesystem. So, any paths containing single or double dots, tildes and/or symbolic links will be resolved to a full path.

Here's a script demonstrating each of these,

echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

Running this script in my home dir, using a relative path:

>>>$ ./ 
pwd: /Users/phatblat
$0: ./
dirname: .
dirname/readlink: /Users/phatblat

Again, but using the full path to the script:

>>>$ /Users/phatblat/ 
pwd: /Users/phatblat
$0: /Users/phatblat/
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Now changing directories:

>>>$ cd /tmp
>>>$ ~/ 
pwd: /tmp
$0: /Users/phatblat/
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

And finally using a symbolic link to execute the script:

>>>$ ln -s ~/
>>>$ ./ 
pwd: /tmp
$0: ./
dirname: .
dirname/readlink: /Users/phatblat

@T.L 2012-01-11 09:14:04

readlink will not availabe in some platform in default installation. Try to avoid using it if you can

@Catskul 2013-09-17 19:40:27

be careful to quote everything to avoid whitespace issues: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

@cucu8 2014-11-26 09:29:32

In OSX Yosemite 10.10.1 -f is not recognised as an option to readlink. Using stat -f instead does the job. Thanks

@robert 2016-01-14 20:16:28

In OSX, there is greadlink, which is basically the readlink we are all familiar. Here is a platform independent version: dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`

@phatblat 2016-01-15 21:27:26

Good call, @robert. FYI, greadlink can easily be installed through homebrew: brew install coreutils

@svvac 2016-06-30 16:52:43

Note that $0 doesn't work if the file is sourced. You get -bash instead of the script name.

@Dave Stern 2017-01-25 17:41:12

In OSX, you can use realpath or grealpath, which will give you the resolved path from the root of the file system. Example: cd /usr/bin; realpath ls /usr/bin/ls

@Adam Burley 2018-01-24 12:09:10

@T.L readlink is part of the coreutils package which contains the base GNU utilities. No idea why it wouldn't be available on a GNU-based system. I think we can safely assume OP is using GNU, since the question is about GNU Bash?

@Alexar 2018-04-07 00:33:43

$(dirname $(readlink -f $0)) was my answer, thank you so much!

@Domi 2019-12-26 08:34:40

Python was mentioned a few times. Here is the JavaScript (i.e. node) alternative:

baseDirRelative=$(dirname "$0")
baseDir=$(node -e "console.log(require('path').resolve('$baseDirRelative'))") # get absolute path using node

echo $baseDir

@Alexander Stohr 2019-10-25 16:32:24

disclaimer - the top response does not work in all cases... that's why posting this alternate answer.

as i had problems with the BASH_SOURCE with included 'cd' approach on some very fresh and also on less fresh installed Ubuntu Xenial (16.04) systems when invoking the shell script by means of "sh" i tried out something different that as of now seems to run quite smoothly for my purposes. The approach is a bit more compact in the script and is further much lesser cryptic feeling.

this alternate approach uses the external applications 'realpath' and ''dirname from the coreutils package. (okay, not anyone likes the overhead of invoking secondary processe - but when seeing the multi-line scripting for resolving the true object it wont be that bad either having it solve in a single binary usage.)

so lets see one example of those alternate solution for the described task of querying the true absolute path to the a certain file:

SCRIPT=`realpath -s $0`

Or when having the chance of using paths with spaces (or maybe other special chars):

SCRIPT=`realpath -s "$0"`

indeed, if you dont need the value of the SCRIPT variable then you might be able to merge this two-liner into even a single line. but why really shall you spend the effort for this?

@neu242 2019-12-04 13:08:53

This question is bash specific. If you invoke a script with sh, the shell might be something else, such as zsh or dash.

@Brad Parks 2019-10-08 12:37:16

The following will return the current directory of the script

  • works if it's sourced, or not sourced
  • works if run in the current directory, or some other directory.
  • works if relative directories are used.
  • works with bash, not sure of other shells.
/tmp/a/b/c $ . ./

/tmp/a/b/c $ . /tmp/a/b/c/

/tmp/a/b/c $ ./

/tmp/a/b/c $ /tmp/a/b/c/

/tmp/a/b/c $ cd

~ $ . /tmp/a/b/c/

~ $ . ../../tmp/a/b/c/

~ $ /tmp/a/b/c/

~ $ ../../tmp/a/b/c/

#!/usr/bin/env bash

# snagged from:
function toAbsPath {
    local target

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

function getScriptDir(){
  local SOURCED
  local RESULT
  (return 0 2>/dev/null) && SOURCED=1 || SOURCED=0

  if [ "$SOURCED" == "1" ]
    RESULT=$(dirname "$1")
    RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
  toAbsPath "$RESULT"

SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"

@MatteoBee 2019-08-27 00:31:07

This is what I crafted throughout the years to use as a header on my bash scripts:

## BASE BRAIN - Get where you're from and who you are.
ORIGINAL_DIR="$(pwd)" # This is not a hot air balloon ride..
fa="$0" # First Assumption
ta= # Temporary Assumption
wa= # Weighed Assumption
while true; do
    [ "${fa:0:1}" = "/" ] && wa=$0 && break
    [ "${fa:0:2}" = "./" ] && ta="${ORIGINAL_DIR}/${fa:2}" && [ -e "$ta" ] && wa="$ta" && break
    ta="${ORIGINAL_DIR}/${fa}" && [ -e "$ta" ] && wa="$ta" && break
SWDIR="$(dirname "$wa")"
SWBIN="$(basename "$wa")"
unset ta fa wa
( [ ! -e "$SWDIR/$SWBIN" ] || [ -z "$SW" ] ) && echo "I could not find my way around :( possible bug in the TOP script" && exit 1

at this point your variables SW SWDIR and SWBIN contain what you need.

@LozanoMatheus 2019-08-26 15:01:25

You can do that just combining the script name ($0) with realpath and/or dirname. It works for Bash and Shell.

#!/usr/bin/env bash

RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"

echo "FULL_PATH->${FULL_PATH}<-"

The output will be something like this:

# RELATIVE_PATH->./bin/<-
# FULL_DIR_PATH->/opt/my_app/bin<-
# FULL_PATH->/opt/my_app/bin/<-

$0 is the name of the script itself

An example:

@bestOfSong 2019-08-15 05:53:26

I want to comment on the previous answer up there ( but don't have enough reputation to do that.

Found a solution for this two years ago on apple's documentation site: . And I stuck to this method afterwards. It cannot handle soft link but otherwise works pretty well for me. I'm posting it here for any who needs it and as a request for comment.


# Get an absolute path for the poem.txt file.

# Get an absolute path for the script file.
SCRIPT="$(which $0)"
if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; then

As shown by the code, after you get the absolute path of the script, then you can use dirname command to get the path of the directory.

@kenorb 2013-11-28 12:05:56

Try the following cross-compatible solution:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

as the commands such as realpath or readlink could be not available (depending on the operating system).

Note: In Bash, it's recommended to use${BASH_SOURCE[0]} instead of $0, otherwise path can break when sourcing the file (source/.).

Alternatively you can try the following function in bash:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"

This function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD variable + filename argument (without ./ prefix).


@Chris 2015-03-27 16:54:26

Please explain more about the realpath function.

@kenorb 2015-03-27 17:35:01

@Chris realpath function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD + filename (without ./ prefix).

@Jakub Jirutka 2015-09-08 20:59:09

Your cross-compatible solution doesn’t work when the script is symlinked.

@Andry 2019-05-07 22:06:25

There is no 100% portable and reliable way to request a path to a current script directory. Especially between different backends like cygwin/mingw/msys/Linux and etc. This issue was not properly and completely resolved in the bash for ages.

For example, this could not be resolved if you want to request the path after the source command to make nested inclusion of another bash script which is in turn use the same source command to include another bash script and so on.

In case of source command i suggest to replace the source command to something like this:

function include()
  if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
    local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
    local include_file_path=...
    local dir_path=... request the directory from the "$1" argument using one of answered here methods...
    local include_file_path=...
  ... push $CURRENT_SCRIPT_DIR in to stack ... 
  export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
  source "$include_file_path"
  ... pop $CURRENT_SCRIPT_DIR from stack ...

From now and on the use of include(...) is based on previous CURRENT_SCRIPT_DIR in your script.

This only works when you can replace all source commands by include command. If you can't, then you have no choice. At least until developers of the bash interpreter make an explicit command to request the current running script directory path.

My own closest implementation to this:

@Chaim Leib Halbert 2019-05-22 19:57:49

Here's a command that works under either bash or zsh, and whether executed standalone or sourced:

[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \
    || this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")

How it works

The zsh current file expansion: ${(%):-%x}

${(%):-%x} in zsh expands to the path of the currently-executing file.

The fallback substitution operator :-

You know already that ${...} substitutes variables inside of strings. You might not know that certain operations are possible (in both bash and zsh) on the variables during substitution, like the fallback expansion operator :-:

% x=ok
% echo "${x}"

% echo "${x:-fallback}"

% x=
% echo "${x:-fallback}"

% y=yvalue
% echo "${x:-$y}"

The %x prompt escape code

Next, we'll introduce prompt escape codes, a zsh-only feature. In zsh, %x will expand to the path of the file, but normally this is only when doing expansion for prompt strings. To enable those codes in our substitution, we can add a (%) flag before the variable name:

% cat apath/
echo "${(%)fpath}"

% source apath/

% cd apath
% source

An unlikely match: the percent escape and the fallback

What we have so far works, but it would be tidier to avoid creating the extra fpath variable. Instead of putting %x in fpath, we can use :- and put %x in the fallback string:

% cat
echo "${(%):-%x}"

% source

Note that we normally would put a variable name between (%) and :-, but we left it blank. The variable with a blank name can't be declared or set, so the fallback is always triggered.

Finishing up: what about print -P %x?

Now we almost have the directory of our script. We could have used print -P %x to get the same file path with fewer hacks, but in our case, where we need to pass it as an argument to dirname, that would have required the overhead of a starting a new subshell:

% cat apath/
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "${(%):-%x}"

% source apath/

It turns out that the hacky way is both more performant and succinct.

@cdonat 2019-05-17 12:09:27

This is a pretty old question, but I'll add my answer anyway. I usually use

dirname $(which $BASH_SOURCE)

@Top-Master 2019-03-26 07:50:18

Below stores the script's directory path in dir variable

(also it tries to support being executed in Cygwin using Windows php)

and at last it runs the my-sample-app executable with all arguments passed to this script using "[email protected]"

#!/usr/bin/env sh

dir=$(cd "${0%[/\\]*}" > /dev/null && pwd)

if [ -d /proc/cygdrive ]; then
    case $(which php) in
        $(readlink -n /proc/cygdrive)/*)
            # We are in Cygwin using Windows php, so the path must be translated
            dir=$(cygpath -m "$dir");

# Runs the executable which is beside this script
"${dir}/my-sample-app" "[email protected]"

@danemacmillan 2019-01-10 16:33:48

The chosen answer works very well. I'm posting my solution for anyone looking for shorter alternatives that still addresses sourcing, executing, full paths, relative paths, and symlinks. Finally, this will work on MacOS, given that it cannot be assumed that GNU's coreutils' version of readlink is available.

The gotcha is that it's not using Bash, but is easy to use in a Bash script. While OP did not place any constraints on the language of the solution, it's probably best that most have stayed within the Bash world. This is just an alternative, and possibly an unpopular one.

PHP is available on MacOS by default, and installed on a number of other platforms, though not necessarily by default. I realize this is a shortcoming, but I'll leave this here for any people coming from search engines, anyway.

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"

@DmitryBorodin 2019-02-24 22:14:20

There was nothing about MacOS in the question. I think this answer should start with "Another approach is to use PHP instead of relying on BASH". Because this reveals only at the end of the answer.

Related Questions

Sponsored Content

21 Answered Questions

[SOLVED] How do I list all files of a directory?

  • 2010-07-08 19:31:22
  • duhhunjonn
  • 4708819 View
  • 3470 Score
  • 21 Answer
  • Tags:   python directory

26 Answered Questions

[SOLVED] How can I safely create a nested directory?

35 Answered Questions

[SOLVED] How can I add an empty directory to a Git repository?

  • 2008-09-22 16:41:03
  • Laurie Young
  • 1026302 View
  • 4372 Score
  • 35 Answer
  • Tags:   git directory git-add

35 Answered Questions

[SOLVED] How can I check if a directory exists in a Bash shell script?

  • 2008-09-12 20:06:25
  • Grundlefleck
  • 2839610 View
  • 3806 Score
  • 35 Answer
  • Tags:   bash shell unix posix

46 Answered Questions

[SOLVED] How can I count all the lines of code in a directory recursively?

  • 2009-08-31 17:42:20
  • user77413
  • 815839 View
  • 1668 Score
  • 46 Answer
  • Tags:   bash shell

20 Answered Questions

[SOLVED] Get current directory name (without full path) in a Bash script

  • 2009-09-03 03:11:53
  • Derek Dahmer
  • 697902 View
  • 850 Score
  • 20 Answer
  • Tags:   bash shell

37 Answered Questions

[SOLVED] How can I check if a program exists from a Bash script?

  • 2009-02-26 21:52:49
  • gregh
  • 728703 View
  • 2307 Score
  • 37 Answer
  • Tags:   bash

26 Answered Questions

[SOLVED] How to check if a string contains a substring in Bash

Sponsored Content