Getting the source directory of a Bash script from within

4 134

1 561

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

For instance, let's say 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


Posted 2008-09-12T20:39:56.860

Reputation: 44 160

57None of the current solutions work if there are any newlines at the end of the directory name - They will be stripped by the command substitution. To work around this you can append a non-newline character inside the command substitution - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)" - and remove it without a command substitution - DIR="${DIR%x}". – l0b0 – 2012-09-24T12:15:09.277

71@jpmc26 There are two very common situations: Accidents and sabotage. A script shouldn't fail in unpredictable ways just because someone, somewhere, did a mkdir $'\n'. – l0b0 – 2013-03-28T08:14:48.753

Tyipcally, the caller of the script can easily provide the input to that script it needs, such as "/home/userx/bin/my_script /home/userx/bin" since the caller knows the path it's using to access the script. Because Linux uses the inode model from Unix (awesome feature), it's often tricky to find the answer automatically. And, even using the techniques mentioned below, it can be tricky. For example, a script can reside at more than one path (ln without -s). If the fragility those considerations add is not a concern, the answers using BASH_SOURCE and the like do the job. – ash – 2013-08-22T19:11:47.350

ash: With hardlinks, you'll get one of the correct answers - the path which was used for invocation. If hardlinks are a problem, don't use them (they're probably a bad idea for scripts anyway, in most cases). – Blaisorblade – 2013-09-26T23:57:51.120

@l0b0 I used your solution on RHEL 6.5x64, but my $DIR variable ends up with a newline after it anyway. Any idea why that might be? – Ryan – 2014-07-01T16:17:46.653

@l0b0 I've posted a question:

– Ryan – 2014-07-02T17:08:27.677

See also: Bash script: set current working directory to the directory of the script.

– kenorb – 2014-09-20T14:25:26.613

13anyone who lets people sabotage their system in that way shouldn't leave it up to bash to detect such problems... much less hire people capable of making that kind of mistake. I have never had, in the 25 years of using bash, seen this kind of thing happen anywhere.... this is why we have things like perl and practices such as taint checking (i will probably be flamed for saying that :) – osirisgothra – 2015-02-05T00:12:36.717

2@l0b0 Consider that you'd need the same protection on dirname, and that the directory could start with a - (e.g. --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Perhaps this is overkill? – Score_Under – 2015-04-28T17:46:47.727


Guys, you should check out my simple script here:, it handles symlinks, relative paths, absolute paths, tested on linux and macOsX.

– ling – 2015-10-02T22:07:11.977


I stronly suggest to read this Bash FAQ about the subject.

– Rany Albeg Wein – 2016-01-30T02:22:03.140

@Blaisorblade writes "If hardlinks are a problem, don't use them". That is not possible. Every entry in a directory is a hard link. Every path to a file is a hard link. Without hard links, you cannot access a file. The problem with this question is in the wording: files are not stored in directories. Directories are nothing but a list of hard links. – William Pursell – 2016-08-27T20:37:38.647

1@WilliamPursell Then read that as "avoid adding a second hardlink to the file", which I think was clear in context and is a standard abuse of language. The Bash FAQ lists other and more relevant concerns, though it seems more bent on discouraging this than describing when this is safe. – Blaisorblade – 2016-08-28T12:13:41.197

Is there a reason no one said realpath "$0"? – Nonny Moose – 2017-06-09T23:13:18.657

For the filename, see: How do I know the script file name in a Bash script?

– kenorb – 2017-07-19T18:40:15.960

1I wonder why this is not a function of Bash itself. – Ondra Žižka – 2018-07-29T01:04:38.543

@osirisgothra First, the mindset of not making your shell scripts robust like this is literally a regular source of security exploits world-wide. Second, everyone "lets" people sabotage their systems like that, and everyone makes the kind of mistakes that put unexpected characters into pathnames, even you: human error and existence of security exploits in modern complex internet connected systems are effectively guaranteed given enough time - the only variable is how lucky we are and the only choice how much we willfully neglect to make robust the pieces we do have control over. – mtraceur – 2018-10-18T21:10:46.700

@Score_Under Just don't use dirname at all: just use reldir=${0%/*} directly – mtraceur – 2018-10-18T21:14:03.207


5 491

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && 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 && 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 && 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.

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" )" && 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'


Posted 2008-09-12T20:39:56.860

Reputation: 186 293

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 
    SOURCE="$(readlink "$SOURCE")"; 
    if [ "${SOURCE:0:1}" != / ] ; then
        SOURCE="$(dirname "$LAST_SOURCE")/$SOURCE"; 
 – beltorak  – 2011-12-15T09:47:26.413

3When 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) – rsaw – 2012-03-08T18:54:04.917

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 )" – fileoffset – 2012-03-26T03:50:28.980

4To deal with a relative symlink, I found I needed DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd -P "$( dirname "$SOURCE" )" && pwd )" – Joseph Wright – 2012-04-15T19:35:06.910

@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. – dubiousjim – 2012-05-31T17:21:15.390

@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. – rsaw – 2012-05-31T23:48:53.563

@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. – dubiousjim – 2012-06-01T00:15:18.363

1@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.) – rsaw – 2012-06-01T23:36:22.657

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. – Qwertie – 2012-06-20T21:59:29.897

3this not works when using source /path/scriptname – hugemeow – 2012-09-19T09:30:03.160

1Do not cd to other directory before this code, otherwise it will be wrong. – weynhamz – 2012-12-25T12:13:16.703

5Note 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. – JeffCharter – 2013-01-11T23:12:38.160

17Sometimes 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 )" – user716468 – 2013-02-03T02:33:52.163

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. – Eonil – 2013-04-03T09:41:13.660

@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-17T16:58:04.140

1Is 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. – Chris Quenelle – 2013-04-17T16:59:27.663


@ChrisQuenelle: ${BASH_SOURCE[0]} differs from $0 when using source.

– Matt Kantor – 2013-04-28T20:14:11.023

2the 7 millionth time i come here to copy this bit i will figure out why people need the 2nd form for linked files... – Prospero – 2013-05-22T01:21:45.450

@DaveDopson: I suggest to add >/dev/null after the cd because the above breaks in odd ways when cd prints something to stdout. – Aaron Digulla – 2013-11-04T13:40:40.443

1In my experience, it is not necessary to add quotes around $() expressions. Thus: FILES=$(ls -1) and FILES="$(ls -1)" are exactly the same. – kevinarpe – 2013-11-24T07:19:27.913

1A 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. – Zitrax – 2014-02-17T14:40:46.560

3Why not use the following command? DIR=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") – x-yuri – 2014-04-29T15:18:51.737

Why ${BASH_SOURCE[0]}? Why not just ${BASH_SOURCE}? – cnst – 2014-05-14T19:50:09.230

83Wait, so what is the final command to use? – Xeoncross – 2014-06-05T14:19:19.840


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?

– jpmc26 – 2014-08-14T15:13:30.877

3@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. – slm – 2014-09-02T19:52:53.127

Sadly, this will not work from ~/.bashrc because the $DIR variable is only evaluated once at login time, not dynamically all the time. :-( – lpapp – 2014-10-20T17:09:41.587

@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. – jpmc26 – 2014-12-03T04:00:58.050

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]}" ) – Pete – 2015-05-06T12:02:02.237

2I've found myself coming back to this question over and over again, just to copy a string and remove spaces that seem unnecessary to me. To simplify my copy-and-pastes in the future, here's OP's answer with "proper" (warning: biased) spacing:

DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) – Chiru – 2015-06-04T17:25:44.770


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

– tvlooy – 2015-06-09T19:32:10.217

109@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. – Adrian Günter – 2015-10-28T23:38:56.930

9Ah, space. The final frontier ... :) You are right, I'll fix it in the gist. – tvlooy – 2015-10-29T09:09:42.523

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. – gouessej – 2015-10-29T10:39:54.537

2As 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-16T14:02:54.220

1As Chiru has pointed out, 700,000 people are looking at an answer that has unnecessary spaces. – H2ONaCl – 2016-01-26T04:27:41.330

2By 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-30T02:16:59.393

To deal with symlinks, use pwd -P – Jesin – 2016-09-06T00:29:24.437

A good reader on this:

– Ode – 2016-11-21T18:28:00.767

Your one-liner is the only one of all the answers here that really work in all cases, very good! but it can be written shorter (see my edit request). – switch87 – 2017-03-13T16:56:40.953

@switch87 your solution doesn't work in all cases. Please do not edit the answer if you are not 100% sure. – smancill – 2017-03-20T03:23:38.877

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

3@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. – Alexander Ljungberg – 2017-05-09T16:14:06.757

All BSD's have it. OSX doesn't. Install coreutils and use greadline – tvlooy – 2017-05-09T20:31:53.760

Cute format: DIR=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd) – zored – 2017-07-17T11:00:22.460

1Seems 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? – tishma – 2017-09-09T08:47:30.947

How about dirname $0 ? – jojek – 2017-09-19T11:26:59.223

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.

– Novice C – 2017-09-27T12:26:21.100

FILE_DIR=$(dirname "`readlink -f \"$0\"`") #worked for me – Cedric – 2017-10-11T12:07:31.817

1So, @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). – Ethan Reesor – 2017-11-08T05:13:54.360

Why not use realpath (as in the other answers) instead of the while loop? – Vladimir Panteleev – 2017-12-12T21:31:16.670

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 – Jim Garrison – 2011-08-09T16:07:27.053

@tishma you're right, it doesn't change the current directory – CharlesB – 2018-01-25T09:26:06.673

2For one last upgrade, try this: DIR="$( cd -P "$( dirname "$0" )" && pwd )" --- that will give you the absolute dereferenced path, ie, resolves all symlinks. – Dave Dopson – 2011-08-16T18:03:08.967

ps., that's freaking brilliant. I had to wrap my brain into a pretzel to understand the evaluation order of those nested quotes. Hell of a way to make a 1-liner! – Dave Dopson – 2011-08-16T18:05:56.733

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. – eold – 2011-08-18T22:00:00.377

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 – muni764 – 2018-08-21T14:16:39.410

Bad, it's too easy to get a bash: dirname: No such file or directory – Mathieu CAROFF – 2018-11-13T18:37:50.100

Note: don't forget the #!/bin/bash at the top. #!/bin/sh won't work. You'll get a Bad substitution error. – Gabriel Staples – 2018-12-02T05:41:52.890

20You 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)". – Dan Moulding – 2011-10-19T15:54:43.013


Use dirname "$0":

echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

using pwd alone will not work if you are not running the script from the directory it is contained in.

[[email protected] ~]$ pwd
[[email protected] ~]$ ./
The script you are running has basename, dirname .
The present working directory is /home/matt
[[email protected] ~]$ cd /tmp
[[email protected] tmp]$ ~/
The script you are running has basename, dirname /home/matt
The present working directory is /tmp

matt b

Posted 2008-09-12T20:39:56.860

Reputation: 109 276

3$0 gives wrong result in sourced scripts (including bashrc) – ivan_pozdeev – 2012-01-11T09:02:36.293

7@Darron: you can only use type -p if the script is executable. This can also open a subtle hole if the script is executed using bash and there is another script with the same name executable somewhere else. – D.Shawley – 2010-02-05T12:18:04.317

Cool, this doesn't resolve symlinks, which is exactly what I was looking for. – Ehtesh Choudhury – 2013-03-14T16:45:38.590

3@T.J. Crowder > But surely there's a better way to expand that path? Yes, you can use $(readlink -f "$0") to get full path in canonical form with all symlinks resolved. Solves tons of problems. – Volodymyr M. Lisivka – 2013-07-09T15:33:39.703

NOTE ${BASH_SOURCE[0]} is different from $0 when you include one bash file into another using . or source. – Du Song – 2013-07-21T05:55:46.240

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; [ -h "$DIR/$0" ] && DIR=readlink -n "$DIR/$0" – Prospero – 2013-11-18T20:16:29.743

72@Darron: but since the question is tagged bash and the hash-bang line explicitly mentions /bin/bash I'd say it's pretty safe to depend on bashisms. – Joachim Sauer – 2010-06-11T12:56:58.733

Even with bash, $0 is not always enough. A script may source another script! – reinierpost – 2014-03-28T13:08:25.920


@T.J.Crowder @VolodymyrM.Lisivka you could also use $(realpath "$0") but note that neither readlink nor readpath will work on Mac OSX.

– joeytwiddle – 2015-01-27T07:04:14.180


+1, but the problem with using dirname $0 is that if the directory is the current directory, you'll get .. That's fine unless you're going to change directories in the script and expect to use the path you got from dirname $0 as though it were absolute. To get the absolute path: pushd \dirname $0` > /dev/null,SCRIPTPATH=`pwd`,popd > /dev/null`: (But surely there's a better way to expand that path?)

– T.J. Crowder – 2011-01-23T10:30:43.260

6@T.J. Crowder I'm not sure sure dirname $0 is a problem if you assign it to a variable and then use it to launch a script like $dir/; I would imagine this is the use case for this type of thing 90% of the time. ./ would work fine. – matt b – 2011-01-24T12:55:08.593

@matt b: As I said, it's fine as long as your script using it doesn't change the current directory. If your script does, then you try to launch ./ and is meant to be in the same directory as the original, it's going to fail because you're no longer there. See also:

– T.J. Crowder – 2011-01-24T13:00:29.530

1@mattb This version failed with directory names that contain special chars and double quote char. e.g. mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")" – Jay jargot – 2016-03-31T08:32:41.030

To get this working on Mac OS, probably because of spaces in the path, I had to enclose the argument with quotes, like so: \dirname "$0"``. – Demis – 2016-06-01T03:06:36.933

echo "I am at \pwd`/`basename $0`"` worked for me – NecipAllef – 2018-10-19T12:24:10.197

23For portability beyond bash, $0 may not always be enough. You may need to substitute "type -p $0" to make this work if the command was found on the path. – Darron – 2008-10-23T20:15:01.783


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


Posted 2008-09-12T20:39:56.860

Reputation: 2 871

8readlink will not availabe in some platform in default installation. Try to avoid using it if you can – T.L – 2012-01-11T09:14:04.400

28be careful to quote everything to avoid whitespace issues: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" – Catskul – 2013-09-17T19:40:27.723

8In OSX Yosemite 10.10.1 -f is not recognised as an option to readlink. Using stat -f instead does the job. Thanks – cucu8 – 2014-11-26T09:29:32.133

5In 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]}`` – robert – 2016-01-14T20:16:28.140

4Good call, @robert. FYI, greadlink can easily be installed through homebrew: brew install coreutils – phatblat – 2016-01-15T21:27:26.973

1Note that $0 doesn't work if the file is sourced. You get -bash instead of the script name. – svvac – 2016-06-30T16:52:43.827

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 – Dave Stern – 2017-01-25T17:41:12.203

@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? – Kidburla – 2018-01-24T12:09:10.720

$(dirname $(readlink -f $0)) was my answer, thank you so much! – Alexar – 2018-04-07T00:33:43.040


pushd . > /dev/null
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
cd `dirname ${SCRIPT_PATH}` > /dev/null
popd  > /dev/null

Works for all versions,including

  • when called via multple depth soft link,
  • when the file it
  • when script called by command "source" aka . (dot) operator.
  • when arg $0 is modified from caller.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternatively, if the bash script itself is a relative symlink you want to follow it and return the full path of the linked-to script:

pushd . > /dev/null
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
cd `dirname ${SCRIPT_PATH}` > /dev/null
popd  > /dev/null

SCRIPT_PATH is given in full path, no matter how it is called.
Just make sure you locate this at start of the script.

This comment and code Copyleft, selectable license under the GPL2.0 or later or CC-SA 3.0 (CreativeCommons Share Alike) or later. (c) 2008. All rights reserved. No warranty of any kind. You have been warned.


Posted 2008-09-12T20:39:56.860

Reputation: 301

4Nice! Could be made shorter replacing "pushd[...] popd /dev/null" by SCRIPT_PATH=readlink -f $(dirname "${VIRTUAL_ENV}"); – e-satis – 2009-11-29T11:34:04.057

3It's dangerous for a script to cd out of its current directory in the hope of cding back again later: The script may not have permission to change directory back to the directory that was current when it was invoked. (Same goes for pushd/popd) – Adrian Pronk – 2012-11-06T01:15:21.763

1This is by far the most "stable" version I've seen. Thank you! – Tomer Gabel – 2010-01-26T08:19:46.453

Instead of the if and the while loop, simply readlink -f – pihentagy – 2014-03-12T09:47:37.427

does it work if I run script like utils/ ? – Paolo – 2014-05-17T11:40:23.333

Should not the colon in the line 1 be moved in line two between the if- and then-statements? – ovanes – 2010-08-18T10:08:00.613

5And instead of using pushd ...; would not it be better to use $(cd dirname "${SCRIPT_PATH}" && pwd)? But anyway great script! – ovanes – 2010-08-18T10:16:00.127

5readlink -f is GNU-specific. BSD readlink does not have that option. – matt brennan – 2014-06-03T16:48:15.073

2What's with all the unnecessary subshells? ([ ... ]) is less efficient than [ ... ], and there's no advantage taken of the isolation offered in return for that performance hit here. – Charles Duffy – 2014-06-09T03:37:19.347

The list of cases handled (Works for all versions) above has one item == when the file it. Can anyone appropriately complete that sentence fragment? – TomRoche – 2017-10-31T21:22:37.477

by far, the best – Vivick – 2017-11-09T20:49:01.240

8Isn't the if redundant? while is testing the same thing... – gatopeich – 2011-08-05T13:28:49.830

Just to note my use case: I saved the script as ~/getpath; and then made a bash alias: echo "alias getpath='"'GP="getpath" ; cp ~/$GP ./$GP ; source ./$GP ; rm ./$GP'"'" >> ~/.bashrc ... and now I can do, in either shell or script: getpath ; echo $SCRIPT_PATH . Thanks for the script - cheers! – sdaau – 2011-08-06T13:43:50.060

Ups, shame I missed my edit time window - actually, my alias comment above doesn't work in a script, not even with shopt -s expand_aliases (see Aliases)

– sdaau – 2011-08-06T13:50:36.107

1You can fuse this approach with the answer by ddopson to arrive at a solution that is a simple one-liner: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)". – Dan Moulding – 2011-10-19T15:55:55.727


Short answer:

`dirname $0`

or (preferably):

$(dirname "$0")


Posted 2008-09-12T20:39:56.860

Reputation: 3 206

12It won't work if you source the script. "source my/" – Arunprasad Rajkumar – 2014-02-05T07:34:32.437

5then nothing will – vidstige – 2016-03-03T10:14:27.613

I use this all the time in my bash scripts that automate stuff and often invoke other scripts in the same dir. I'd never use source on these and cd $(dirname $0) is easy to remember. – guaka – 2017-01-03T16:28:27.610

works for /bin/sh – shadi – 2017-05-29T06:25:50.910

10@vidstige: ${BASH_SOURCE[0]} instead of $0 will work with source my/ – Timothy Jones – 2017-09-27T06:48:00.680

@TimothyJones that will fail 100% of the time if sourced from any other shell than bash. ${BASH_SOURCE[0]} is not satisfactory at all. ${BASH_SOURCE:-0} is much better. – Mathieu CAROFF – 2018-11-13T18:43:20.300


You can use $BASH_SOURCE


scriptdir=`dirname "$BASH_SOURCE"`

Note that you need to use #!/bin/bash and not #!/bin/sh since its a bash extension

Mr Shark

Posted 2008-09-12T20:39:56.860

Reputation: 11 358

9When I do ./foo/script, then $(dirname $BASH_SOURCE) is ./foo. – Till – 2010-10-25T17:06:20.367

also works with source / . operator! – grosser – 2011-08-11T20:53:38.603


This should do it:

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

Works with symlinks and spaces in path. See 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?

Simon Rigét

Posted 2008-09-12T20:39:56.860

Reputation: 1 294


pwd can be used to find the current working directory, and dirname to find the directory of a particular file (command that was run, is $0, so dirname $0 should give you the directory of the current script).

However, dirname gives precisely the directory portion of the filename, which more likely than not is going to be relative to the current working directory. If your script needs to change directory for some reason, then the output from dirname becomes meaningless.

I suggest the following:


reldir=`dirname $0`
cd $reldir

echo "Directory is $directory"

This way, you get an absolute, rather then relative directory.

Since the script will be run in a separate bash instance, there is no need to restore the working directory afterwards, but if you do want to change back in your script for some reason, you can easily assign the value of pwd to a variable before you change directory, for future use.

Although just

cd `dirname $0`

solves the specific scenario in the question, I find having the absolute path to more more useful generally.


Posted 2008-09-12T20:39:56.860

Reputation: 12 519

7You can do it all in one line like this:

DIRECTORY=$(cd dirname $0 && pwd) – dogbane – 2008-10-29T08:38:07.750

This doesn't work if the script sources another script and you want to know the name of the latter. – reinierpost – 2014-03-28T13:10:27.913


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




In the first and third ways $0 doesn't have the full path info. In the second and third, pwd do not work. The only way to get the dir 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 dir, and reference it by full path. Data shoudn't be in the /usr/bin dir anyway, so this is probably the thing to do.


Posted 2008-09-12T20:39:56.860


8If you intend to disprove his comment, PROVE that a script CAN access where it's stored with a code example. – Richard Duerr – 2015-11-18T18:54:12.700


SCRIPT_DIR=$( cd ${0%/*} && pwd -P )


Posted 2008-09-12T20:39:56.860

Reputation: 640

This is way shorter than the chosen answer. And appears to work just as well. This deserves 1000 votes just so people do not overlook it. – Patrick – 2013-09-19T03:07:26.623

1As many of the previous answers explain in detail, neither $0 nor pwd are guaranteed to have the right information, depending on how the script is invoked. – IMSoP – 2013-09-23T16:51:44.220


This gets the current working directory on Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)


Posted 2008-09-12T20:39:56.860

Reputation: 1


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


Posted 2008-09-12T20:39:56.860

Reputation: 1


This is Linux specific, but you could use:

SELF=$(readlink /proc/$$/fd/255)

Steve Baker

Posted 2008-09-12T20:39:56.860

Reputation: 3 761

1It's also bash specific, but perhaps bash's behavior has changed? /proc/fd/$$/255 seems to point to the tty, not to a directory. For example, in my current login shell, file descriptors 0, 1, 2, and 255 all refer to /dev/pts/4. In any case, the bash manual doesn't mention fd 255, so it's probably unwise to depend on this behavior.\ – Keith Thompson – 2015-03-29T00:17:33.407

2Interactive shell != script. Anyway realpath ${BASH_SOURCE[0]}; would seem to be the best way to go. – Steve Baker – 2015-04-06T12:52:06.620


Here is a POSIX compliant one-liner:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test


Posted 2008-09-12T20:39:56.860

Reputation: 21

3I had success with this when running a script by itself or by using sudo, but not when calling source ./ – Michael R – 2013-04-17T21:57:40.030

And it fails when cd is configured to print the new path name. – Aaron Digulla – 2013-11-04T13:45:25.597


I tried every one of these and none of them 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 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"`"

Hope that helps

Mike Bethany

Posted 2008-09-12T20:39:56.860


3Doesn't work if the script is being sourced from another script. – reinierpost – 2014-03-28T13:12:03.503


I would use something like this:

# retrieve the full pathname of the called script
scriptPath=$(which $0)

# check whether the path is a link or not
if [ -L $scriptPath ]; then

    # it is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))


    # otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)



Posted 2008-09-12T20:39:56.860

Reputation: 93

This is the real one! Works with simple sh too! Problem with simple dirname "$0" based solutions: If the script is in the $PATH and is invoked without path, they will give wrong result. – Notinlist – 2014-11-18T10:25:02.290

@Notinlist Not so. If the script is found via the PATH, $0 will contain the absolute filename. If the script is invoked with a relative or absolute filename containing a /, $0 will contain that. – Neil Mayhew – 2016-02-03T22:08:15.890


Here is the simple, correct way:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")


  • ${BASH_SOURCE[0]} - the full path to the script. The value of this will be correct even when the script is being sourced, e.g. source <(echo 'echo $0') prints bash, while replacing it with ${BASH_SOURCE[0]} will print the full path of the script. (Of course, this assumes you're OK taking a dependency on Bash.)

  • readlink -f - Recursively resolves any symlinks in the specified path. This is a GNU extension, and not available on (for example) BSD systems. If you're running a Mac, you can use Homebrew to install GNU coreutils and supplant this with greadlink -f.

  • And of course dirname gets the parent directory of the path.

James Ko

Posted 2008-09-12T20:39:56.860

Reputation: 11 330


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.

EDIT: prevent popd after failed pushd, thanks to konsolebox


Posted 2008-09-12T20:39:56.860

Reputation: 1 632

This works perfectly to get the "real" dirname, rather than just the name of a symlink. Thank you! – Jay Taylor – 2010-06-23T20:32:50.107

1Better SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" &gt; /dev/null &amp;&amp; { SCRIPT_DIR=$PWD; popd &gt; /dev/null; } – konsolebox – 2014-07-03T04:15:43.867

@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. – Fuwjax – 2015-01-19T20:03:31.950

@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. – konsolebox – 2015-01-20T19:21:28.617



# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`

scriptdir=`dirname "$PRG"`


Posted 2008-09-12T20:39:56.860



I've compared many of the answers given, and come up with some more compact solutions. These seem to handle all of the crazy edge cases that arise from your favorite combination of:

  • Absolute paths or relative paths
  • File and directory soft links
  • Invocation as script, bash script, bash -c script, source script, or . script
  • Spaces, tabs, newlines, unicode, etc. in directories and/or filename
  • Filenames beginning with a hyphen

If you're running from Linux, it seems that using the proc handle is the best solution to locate the fully resolved source of the currently running script (in an interactive session, the link points to the respective /dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

This has a small bit of ugliness to it, but the fix is compact and easy to understand. We aren't using bash primitives only, but I'm okay with that because readlink simplifies the task considerably. The echo X adds an X to the end of the variable string so that any trailing whitespace in the filename doesn't get eaten, and the parameter substitution ${VAR%X} at the end of the line gets rid of the X. Because readlink adds a newline of its own (which would normally be eaten in the command substitution if not for our previous trickery), we have to get rid of that, too. This is most easily accomplished using the $'' quoting scheme, which lets us use escape sequences such as \n to represent newlines (this is also how you can easily make deviously named directories and files).

The above should cover your needs for locating the currently running script on Linux, but if you don't have the proc filesystem at your disposal, or if you're trying to locate the fully resolved path of some other file, then maybe you'll find the below code helpful. It's only a slight modification from the above one-liner. If you're playing around with strange directory/filenames, checking the output with both ls and readlink is informative, as ls will output "simplified" paths, substituting ? for things like newlines.

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"


Posted 2008-09-12T20:39:56.860

Reputation: 233

I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop. – Dan Dascalescu – 2015-08-15T11:29:23.480

@DanDascalescu Using the one-liner? Or the full code snippet at the bottom? And were you feeding it any tricky pathnames? – billyjmc – 2015-08-19T06:34:53.883

The one line plus another line to echo $resolved, I saved it as d, chmod +x d, ./d. – Dan Dascalescu – 2015-08-20T05:55:38.493

@DanDascalescu The first line in your script needs to be #!/bin/bash – billyjmc – 2015-08-23T05:07:50.760

@DanDascalescu See

– billyjmc – 2015-08-23T05:17:31.473


$_ 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.


Posted 2008-09-12T20:39:56.860

Reputation: 13 293

3It 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. – clacke – 2014-01-31T14:55:13.293


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

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

No need to use BASH_SOURCE when $0 contains the script filename.


Posted 2008-09-12T20:39:56.860

Reputation: 6 854

1unless 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) – osirisgothra – 2015-02-05T00:07:46.667

I added second pair of quotes around dirname call. Needed if the directory path contains spaces. – user1338062 – 2018-05-13T04:35:07.810


So... I believe I've got this one. Late to the party, but I think some will appreciate it being here is them 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]"

Geoff Nixon

Posted 2008-09-12T20:39:56.860

Reputation: 1 787


Try using:

real=$(realpath $(dirname $0))


Posted 2008-09-12T20:39:56.860

Reputation: 1

All I want to know is, why this way is not good? It seemed no bad and correct for me. Could anyone explain why it's downvoted? – Shou Ya – 2012-08-28T15:16:52.263

6realpath is not a standard utility. – Steve Bennett – 2013-05-13T12:06:13.000

On Linux, realpath is a standard utility (part of the GNU coreutils package), but it is not a bash built-in (i.e., a function provided by bash itself). If you're running Linux, this method will probably work, although I'd substitute the $0 for ${BASH_SOURCE[0]} so that this method will work anywhere, including in a function. – Doug Richardson – 2014-07-18T15:53:46.230

2The order of the operations in this answer is wrong. You need to first resolve the symlink, then do dirname because the last part of $0 may be a symlink that points to a file that is not in the same directory as the symlink itself.

The solution described in this answer just gets the path of the directory where the symlink it stored, not the directory of the target.

Furthermore, this solution is missing quoting. It will not work if the path contains special characters. – hagello – 2015-04-13T21:43:23.893


Try the following cross-compatible solution:

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"

as realpath or readlink commands are not always available (depending on the operating system) and ${BASH_SOURCE[0]} is available only in bash shell.

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).



Posted 2008-09-12T20:39:56.860

Reputation: 63 134

Please explain more about the realpath function. – Chris – 2015-03-27T16:54:26.363

1@Chris realpath function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD + filename (without ./ prefix). – kenorb – 2015-03-27T17:35:01.523

Your cross-compatible solution doesn’t work when the script is symlinked. – Jakub Jirutka – 2015-09-08T20:59:09.270


Hmm, if in the path basename & dirname are just not going to cut it and walking the path is hard (what if parent didn't export PATH!). However, the shell has to have an open handle to its script, and in bash the handle is #255.

SELF=`readlink /proc/$$/fd/255`

works for me.


Posted 2008-09-12T20:39:56.860

Reputation: 22 811

I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop, instead of the actual directory I run the script from. – Dan Dascalescu – 2015-08-15T11:30:30.983


This works in bash-3.2:

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

Here's an example of its usage:

Say you have a ~/bin directory, which is in your $PATH. You have script A inside this directory. It sources script ~/bin/lib/B. You know where the included script is relative to the original one (the subdirectory lib), 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.

Matt Tardiff

Posted 2008-09-12T20:39:56.860

Reputation: 4 286

2The 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. – BroSlow – 2014-01-13T22:30:04.497

"Always"? Not at all. which being an external tool, you have no reason to believe it behaves identically to the parent shell. – Charles Duffy – 2014-06-09T03:42:39.933


The best compact solution in my view would be:

"$( cd "$( echo "${BASH_SOURCE[0]%/*}" )"; pwd )"

There is no reliance on anything other than Bash. The use of dirname, readlink and basename will eventually lead to compatibility issues, so they are best avoided if at all possible.


Posted 2008-09-12T20:39:56.860

Reputation: 557

1You probably should add slash to that: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". You'd have problems with root directory if you don't. Also why do you even have to use echo? – konsolebox – 2014-07-02T17:21:42.690

dirname and basename are POSIX standardized, so why avoid using them? Links: dirname, basename – myrdd – 2018-10-29T09:18:16.643


None of these worked for a bash script launched by Finder in OS X - I ended up using:

SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
sed s:.*/Volumes/:/Volumes/:`"

Not pretty, but it gets the job done.


Posted 2008-09-12T20:39:56.860

Reputation: 1


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

Thamme Gowda

Posted 2008-09-12T20:39:56.860

Reputation: 6 272


This is the only way I've found to tell reliably:

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))


Posted 2008-09-12T20:39:56.860

Reputation: 696

1this is giving me the directory with the last entry stripped off, i.e., the path to the container of the container of the script. – tim – 2015-03-28T15:17:43.360


This worked for me when the other answers here did not:

thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath


Posted 2008-09-12T20:39:56.860

Reputation: 3 728


To sum up many answers:

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

Used 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`

The 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



Posted 2008-09-12T20:39:56.860

Reputation: 92


Use a combination of readlink to canonicalize the name (with a bonus of following it back to its source if it is a symlink) and dirname to extract the directory name:

script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"


Posted 2008-09-12T20:39:56.860

Reputation: 398


$0 is not a reliable way to get current script path. For example this is my .xprofile:

echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/ &

cd /tmp && ~/.xprofile && source ~/.xprofile


So please use BASH_SOURCE instead.


Posted 2008-09-12T20:39:56.860

Reputation: 1 384


Here's an excerpt from my answer to shell script: check directory name and convert to lowercase in which I demonstrate not only how to solve this problem with very basic POSIX-specified utilities, I also address how to very simply store the function's results in a returned variable...

...Well, as you can see, with some help, I hit upon a pretty simple and very powerful solution:

I can pass the function a sort of messenger variable and dereference any explicit use of the resulting function's argument's $1 name with eval as necessary, and, upon the function routine's completion, I use eval and a backslashed quoting trick to assign my messenger variable the value I desire without ever having to know its name.

In full disclosure, ... [I found the messenger variable portion of this] and at Rich's sh tricks and I have also excerpted the relevant portion of his page below my own answer's excerpt.

... EXCERPT: ...

Though not strictly POSIX yet, realpath is a GNU core app since 2012. Full disclosure: never heard of it before I noticed it in the info coreutils TOC and immediately thought of [the linked] question, but using the following function as demonstrated should reliably, (soon POSIXLY?), and, I hope, efficiently provide its caller with an absolutely sourced $0:

% _abs_0() { 
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}"; 
> }  
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"

EDIT: It may be worth highlighting that this solution uses POSIX parameter expansion to first check if the path actually needs expanding and resolving at all before attempting to do so. This should return an absolutely sourced $0via a messenger variable (with the notable exception that it will preserve symlinks) as efficiently as I could imagine it could be done whether or not the path is already absolute. ...

(minor edit: before finding realpath in the docs, I had at least pared down my version of [the version below] not to depend on the time field [as it does in the first ps command], but, fair warning, after testing some I'm less convinced ps is fully reliable in its command path expansion capacity)

On the other hand, you could do this:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"

eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

... And from Rich's sh tricks: ...

Returning strings from a shell function

As can be seen from the above pitfall of command substitution, stdout is not a good avenue for shell functions to return strings to their caller, unless the output is in a format where trailing newlines are insignificant. Certainly such practice is not acceptable for functions meant to deal with arbitrary strings. So, what can be done?

Try this:

func () {
body here
eval "$1=\${foo}"

Of course ${foo} could be replaced by any sort of substitution. The key trick here is the eval line and the use of escaping. The “$1” is expanded when the argument to eval is constructed by the main command parser. But the “${foo}” is not expanded at this stage, because the “$” has been quoted. Instead, it’s expanded when eval evaluates its argument. If it’s not clear why this is important, consider how the following would be bad:

foo='hello ; rm -rf /'
eval "$dest=$foo"

But of course the following version is perfectly safe:

foo='hello ; rm -rf /'
eval "$dest=\$foo"

Note that in the original example, “$1” was used to allow the caller to pass the destination variable name as an argument the function. If your function needs to use the shift command, for instance to handle the remaining arguments as [email protected], then it may be useful to save the value of “$1” in a temporary variable at the beginning of the function.


Posted 2008-09-12T20:39:56.860

Reputation: 538


I usually do:

LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/


Posted 2008-09-12T20:39:56.860



This is how I work it on my scripts: pathvar="$( cd "$( dirname $0 )" && pwd )" This will tell you which directory the Launcher (current script) is being executed from.


Posted 2008-09-12T20:39:56.860

Reputation: 27


IF YOUR BASH SCRIPT IS A SYMLINK, then this is the way to do it

#!/usr/bin/env bash

dirn="$(dirname "$0")"
rl="$(readlink "$0")";
exec_dir="$(dirname $(dirname "$rl"))";
X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"

X is the directory that contains your bash script (the original file, not the symlink). I swear to God this works and is the only way I know of doing this properly.

Alexander Mills

Posted 2008-09-12T20:39:56.860

Reputation: 17 196


I usually include the following at the top of my scripts which works in the majority of cases:

[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');

The first line assigns source based on the value of pwd if run from current path or dirname if called from elsewhere.

The second line examines the path to see if it is a symlink and if so, updates SOURCE_DIR to the location of the link itself.

There are probably better solutions out there but this is the cleanest I've managed to come up with myself.


Posted 2008-09-12T20:39:56.860

Reputation: 1 609


function getScriptAbsoluteDir { # fold>>
    # @description used to get the script path
    # @param $1 the script $0 parameter
    local script_invoke_path="$1"
    local cwd=`pwd`

    # absolute path ? if so, the first character is a /
    if test "x${script_invoke_path:0:1}" = 'x/'
        RESULT=`dirname "$script_invoke_path"`
        RESULT=`dirname "$cwd/$script_invoke_path"`
} # <<fold

Stefano Borini

Posted 2008-09-12T20:39:56.860

Reputation: 71 749

sorry I'm a bit of a bash scrip noob, do I call this function by just typing getScriptAbsoluteDir or local currdir='getScriptAbsoluteDir'? – Jiaaro – 2009-11-30T16:04:04.867

3-1: The function keyword isn't available on POSIX shells, and is a needlessly incompatible bash/ksh/&c. extension. Also, if you're on a shell new enough to have that extension, you don't need the test "x[...]" hack. – Charles Duffy – 2014-06-09T03:41:03.793

This almost looks like Python...I can't seem to remember that I've ever used the "local" keyword in a shell script...And to what Charles said, use if [[ "${script_invoke_path:0:1}" == "/" ]]; then and so on. Note the logical double == "equals" operator. – syntaxerror – 2014-09-09T23:31:46.013


Very late to the discussion, but try something like this:

function get_realpath() {

if [[ -f "$1" ]]
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
        # file *must* be local
        local tmppwd="$PWD"
    # file *cannot* exist
    return 1 # failure

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success


function get_dirname(){

local realpath="$(get_realpath "$1")"
if (( $? )) # true when non-zero.
    return $? # failure
echo "${realpath%/*}"
return 0 # success


# Then from the top level:
get_dirname './'

# Or Within a script:
get_dirname "$0"

# Can even test the outcome!
if (( $? )) # true when non-zero.
    exit 1 # failure

These functions and related tools are part of our product that has been made available to the community for free and can be found at GitHub as realpath-lib. It's simple, clean and well documented (great for learning), pure Bash and has no dependencies. Good for cross-platform use too. So for the above example, within a script you could simply:

source '/path/to/realpath-lib'

get_dirname "$0"

if (( $? )) # true when non-zero.
    exit 1 # failure

That's all!


Posted 2008-09-12T20:39:56.860

Reputation: 557


cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`


Posted 2008-09-12T20:39:56.860

Reputation: 1 589


No forks (besides subshell) and can handle "alien" pathname forms like those with newlines as some would claim:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")


Posted 2008-09-12T20:39:56.860

Reputation: 47 144


This solution applies only to bash. Note that the commonly supplied answer ${BASH_SOURCE[0]} won't work if you try to find the path from within a function.

I've found this line to always work, regardless of whether the file is being sourced or run as a script.

dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

If you want to follow symlinks use readlink on the path you get above, recursively or non-recursively.

Here's a script to try it out and compare it to other proposed solutions. Invoke it as source test1/test2/ or bash test1/test2/

# Location: test1/test2/
echo $0
echo $_
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

echo "Testing within function inside"

echo "Testing within function outside"

# Location: test1/test2/
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

The reason the one-liner works is explained by the use of the BASH_SOURCE environment variable and its associate FUNCNAME.


An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.


An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.

[Source: Bash manual]


Posted 2008-09-12T20:39:56.860

Reputation: 1 534


(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 (see a previous answer<1>), 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


Posted 2008-09-12T20:39:56.860

Reputation: 663


Look at the test at bottom with weird directory names.

To change the working directory to the one where the Bash script is located, you should try this simple, tested and verified with shellcheck solution:

#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2

The test:

$ ls 
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko   46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
$ *symlink/application && printf "SUCCESS\n" ""

Jay jargot

Posted 2008-09-12T20:39:56.860

Reputation: 1 819


ME=`type -p $0`
WORK_DIR=$(cd $MDIR && pwd)


Posted 2008-09-12T20:39:56.860


Gives wrong results if script is invoked from its directory with bash – codeforester – 2018-03-02T02:26:31.007


Based on this answer, I suggest the clarified version that get SCRIPT_HOME as the containing folder of any current running bash script

s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`

Nam G VU

Posted 2008-09-12T20:39:56.860

Reputation: 13 268


I want to make sure that the script is running in its directory. So

cd $(dirname $(which $0) )

After this, if you really want to know where the you are running then run the command below.


Robert J

Posted 2008-09-12T20:39:56.860


4How does this differ from other answers? – Mark – 2015-01-20T12:18:20.387


This one-liner works on CYGWIN even if the script has been called from Windows with bash -c <script>:

set mydir="$(cygpath "$(dirname "$0")")"


Posted 2008-09-12T20:39:56.860

Reputation: 3 255



that is the quickest way I know

Paul Savage

Posted 2008-09-12T20:39:56.860

Reputation: 21

3This simply starts from the pwd, and will not return the path where the current script under execution is located. – parvus – 2015-01-02T07:59:26.560