How can I get the source directory of a Bash script from within the script itself?

Created 12.09.2008 20:39
Viewed 1.95M times
5364 votes

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
21
Comments
None 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}". by l0b0, 24.09.2012 12:15
@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'. by l0b0, 28.03.2013 08:14
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. by ash, 22.08.2013 19:11
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). by Blaisorblade, 26.09.2013 23:57
@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? by Ryan, 01.07.2014 16:17
@l0b0 I've posted a question: stackoverflow.com/questions/24537068/… by Ryan, 02.07.2014 17:08
stackoverflow.com/questions/19313056/… by gavenkoa, 07.01.2015 18:09
anyone 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 :) by osirisgothra, 05.02.2015 00:12
@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? by Score_Under, 28.04.2015 17:46
Guys, you should check out my simple script here: github.com/lingtalfi/printScriptDir/blob/master/…, it handles symlinks, relative paths, absolute paths, tested on linux and macOsX. by ling, 02.10.2015 22:07
I stronly suggest to read this Bash FAQ about the subject. by Rany Albeg Wein, 30.01.2016 02:22
@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. by William Pursell, 27.08.2016 20:37
@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. by Blaisorblade, 28.08.2016 12:13
Is there a reason no one said realpath "$0"? by Nonny Moose, 09.06.2017 23:13
For the filename, see: How do I know the script file name in a Bash script? by kenorb, 19.07.2017 18:40
@Score_Under Just don't use dirname at all: just use reldir=${0%/*} directly by mtraceur, 18.10.2018 21:14
realpath "$(dirname "${0}")" by Alberto Salvia Novella, 02.11.2019 21:45
@l0b0 why isn't this an answer? by Rainb, 15.04.2021 07:58
@Rainb I had no idea my comments were popular :) I've posted it as an answer. Editors, if you see this, should I delete the original comments? by l0b0, 15.04.2021 08:48
A robust and Mac/Linux-friendly solution is HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo .)")"; pwd) - explanation (with simpler variations) here: binaryphile.com/bash/2020/01/12/… by Binary Phile, 18.04.2021 13:22
Show remaining 16 comments
Answers 50
57
7111
#!/bin/bash

SCRIPT_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:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
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
done
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:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
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'"
    SOURCE="$TARGET"
  else
    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
  fi
done
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'"
fi
echo "DIR is '$DIR'"

And it will print something like:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
29.10.2008 08:36
Comments
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 myscript.sh by Jim Garrison, 09.08.2011 16:07
For one last upgrade, try this: DIR="$( cd -P "$( dirname "$0" )" && pwd )" --- that will give you the absolute dereferenced path, ie, resolves all symlinks. by Dave Dopson, 16.08.2011 18:03
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. by eold, 18.08.2011 22:00
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)". by Dan Moulding, 19.10.2011 15:54
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 by beltorak, 15.12.2011 09:47
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) by rsaw, 08.03.2012 18:54
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 )" by fileoffset, 26.03.2012 03:50
To deal with a relative symlink, I found I needed DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd -P "$( dirname "$SOURCE" )" && pwd )" by Joseph Wright, 15.04.2012 19:35
@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. by dubiousjim, 31.05.2012 17:21
@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. by rsaw, 31.05.2012 23:48
@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. by dubiousjim, 01.06.2012 00:15
@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.) by rsaw, 01.06.2012 23:36
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. by Qwertie, 20.06.2012 21:59
Do not cd to other directory before this code, otherwise it will be wrong. by weynhamz, 25.12.2012 12:13
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. by JeffCharter, 11.01.2013 23:12
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 )" by user716468, 03.02.2013 02:33
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. by eonil, 03.04.2013 09:41
@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=. by Chris Quenelle, 17.04.2013 16:58
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. by Chris Quenelle, 17.04.2013 16:59
@ChrisQuenelle: ${BASH_SOURCE[0]} differs from $0 when using source. by Matt Kantor, 28.04.2013 20:14
@DaveDopson: I suggest to add >/dev/null after the cd because the above breaks in odd ways when cd prints something to stdout. by Aaron Digulla, 04.11.2013 13:40
In my experience, it is not necessary to add quotes around $() expressions. Thus: FILES=$(ls -1) and FILES="$(ls -1)" are exactly the same. by kevinarpe, 24.11.2013 07:19
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. by Zitrax, 17.02.2014 14:40
Why not use the following command? DIR=$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")") by x-yuri, 29.04.2014 15:18
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? by jpmc26, 14.08.2014 15:13
@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. by slm, 02.09.2014 19:52
Sadly, this will not work from ~/.bashrc because the $DIR variable is only evaluated once at login time, not dynamically all the time. :-( by lpapp, 20.10.2014 17:09
@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. by jpmc26, 03.12.2014 04:00
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]}" ) by Pete, 06.05.2015 12:02
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 gist.github.com/tvlooy/cbfbdb111a4ebad8b93e for a testcase by tvlooy, 09.06.2015 19:32
@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. by Adrian Günter, 28.10.2015 23:38
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. by gouessej, 29.10.2015 10:39
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. by Rany Albeg Wein, 16.01.2016 14:02
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. by Rany Albeg Wein, 30.01.2016 02:16
To deal with symlinks, use pwd -P by Jesin, 06.09.2016 00:29
@switch87 your solution doesn't work in all cases. Please do not edit the answer if you are not 100% sure. by smancill, 20.03.2017 03:23
The oneliner assumes 'cd' is silent. If not you will end up with garbage. Better: DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >>/dev/null && pwd )" by Andreas, 27.04.2017 14:27
@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. by Alexander Ljungberg, 09.05.2017 16:14
All BSD's have it. OSX doesn't. Install coreutils and use greadline by tvlooy, 09.05.2017 20:31
Cute format: DIR=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd) by zored, 17.07.2017 11:00
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? by tishma, 09.09.2017 08:47
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. by Novice C, 27.09.2017 12:26
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). by Ethan Reesor, 08.11.2017 05:13
Why not use realpath (as in the other answers) instead of the while loop? by Vladimir Panteleev, 12.12.2017 21:31
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 by Anton Krug, 21.08.2018 14:16
Note: don't forget the #!/bin/bash at the top. #!/bin/sh won't work. You'll get a Bad substitution error. by Gabriel Staples, 02.12.2018 05:41
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) by Rich Kuzsma, 09.05.2019 15:13
I always change the formatting of this line into: DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" by wieczorek1990, 28.06.2019 09:20
Is it possible to wrap this in a function, or does it have to be copy/pasted all over the place? by Alexander, 12.07.2019 21:27
I just found out that the above does not work when the script is straight out executed rather than sourced: stackoverflow.com/questions/27880087/… by Andrei LED, 27.08.2019 11:49
@Andrei LED, this seems to work for the bash bug case: $(cd $(dirname ${BASH_SOURCE[@]}) &>/dev/null && pwd) by Neil McGill, 24.09.2019 13:04
why ${BASH_SOURCE[0]} instead of simpler $BASH_SOURCE by Nam G VU, 22.11.2019 15:57
For many cases, realpath will work in place of all this; ex: PATH_TO_SCRIPT=$(realpath $0). I found about realpath from here: stackoverflow.com/a/14892459/4561887. I also just posted this as a new answer here: stackoverflow.com/questions/59895/…. by Gabriel Staples, 10.02.2020 19:49
dirname executes an extra process. consider ${BASH_SOURCE[0]%/*} instead. by Serg, 06.04.2020 12:14
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. by MAXdB, 03.07.2020 02:29
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))" by MAXdB, 03.07.2020 02:40
@MAXdB, did you test on osx? The concern was osx. by fuzzyTew, 05.05.2021 15:02
Show remaining 52 comments
17
982

Use dirname "$0":

#!/bin/bash
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.

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp
12.09.2008 20:49
Comments
For 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. by Darron, 23.10.2008 20:15
@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 test2.sh and there is another script with the same name executable somewhere else. by D.Shawley, 05.02.2010 12:18
@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. by Joachim Sauer, 11.06.2010 12:56
+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: pastie.org/1489386 (But surely there's a better way to expand that path?) by T.J. Crowder, 23.01.2011 10:30
@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/script.sh; I would imagine this is the use case for this type of thing 90% of the time. ./script.sh would work fine. by matt b, 24.01.2011 12:55
@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 ./script.sh and script.sh is meant to be in the same directory as the original, it's going to fail because you're no longer there. See also: stackoverflow.com/questions/4774054/… by T.J. Crowder, 24.01.2011 13:00
$0 gives wrong result in sourced scripts (including bashrc) by ivan_pozdeev, 11.01.2012 09:02
Cool, this doesn't resolve symlinks, which is exactly what I was looking for. by Ehtesh Choudhury, 14.03.2013 16:45
@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. by Volodymyr M. Lisivka, 09.07.2013 15:33
NOTE ${BASH_SOURCE[0]} is different from $0 when you include one bash file into another using . or source. by Du Song, 21.07.2013 05:55
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; [ -h "$DIR/$0" ] && DIR=readlink -n "$DIR/$0" by Heavy Gray, 18.11.2013 20:16
Even with bash, $0 is not always enough. A script may source another script! by reinierpost, 28.03.2014 13:08
@T.J.Crowder @VolodymyrM.Lisivka you could also use $(realpath "$0") but note that neither readlink nor readpath will work on Mac OSX. stackoverflow.com/questions/3572030/… by joeytwiddle, 27.01.2015 07:04
@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\3‌​5\36\37\40\41\42\43\‌​44\45\46\47testdir" "")" by Jay jargot, 31.03.2016 08:32
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"`. by Demis, 01.06.2016 03:06
@NecipAllef that works only if the script is in the same directory as you/the caller, if you call /directory/test.sh in / it'll output "I'm at //test.sh" by Muskovets, 10.07.2020 18:17
Have to say it is annoying how full of pitfalls this is... the problem is $0 -- in Windows it's always the script name, so this is very simple: %~dp0 by Paul, 26.08.2020 19:44
Show remaining 12 comments
9
556

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, whatdir.sh:

#!/bin/bash
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:

>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

Again, but using the full path to the script:

>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Now changing directories:

>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

And finally using a symbolic link to execute the script:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
26.09.2009 20:38
Comments
readlink will not availabe in some platform in default installation. Try to avoid using it if you can by T.L, 11.01.2012 09:14
be careful to quote everything to avoid whitespace issues: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" by Catskul, 17.09.2013 19:40
In OSX Yosemite 10.10.1 -f is not recognised as an option to readlink. Using stat -f instead does the job. Thanks by cucu8, 26.11.2014 09:29
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]}` by robert, 14.01.2016 20:16
Good call, @robert. FYI, greadlink can easily be installed through homebrew: brew install coreutils by phatblat, 15.01.2016 21:27
Note that $0 doesn't work if the file is sourced. You get -bash instead of the script name. by svvac, 30.06.2016 16:52
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 by Dave Stern, 25.01.2017 17:41
@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? by Adam Burley, 24.01.2018 12:09
IMHO, this is the most accurate answer. If you don't have readlink, one can use realpath too. Be aware only to use canonical output if needed => readlink -f by LeoRado, 26.04.2021 11:46
Show remaining 4 comments
15
186
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`;
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

It works for all versions, including

  • when called via multiple 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
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
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.

07.10.2008 16:12
Comments
Nice! Could be made shorter replacing "pushd[...] popd /dev/null" by SCRIPT_PATH=readlink -f $(dirname "${VIRTUAL_ENV}"); by e-satis, 29.11.2009 11:34
This is by far the most "stable" version I've seen. Thank you! by Tomer Gabel, 26.01.2010 08:19
Should not the colon in the line 1 be moved in line two between the if- and then-statements? by ovanes, 18.08.2010 10:08
And instead of using pushd ...; would not it be better to use $(cd dirname "${SCRIPT_PATH}" && pwd)? But anyway great script! by ovanes, 18.08.2010 10:16
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! by sdaau, 06.08.2011 13:43
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) by sdaau, 06.08.2011 13:50
You 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)". by Dan Moulding, 19.10.2011 15:55
It'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) by Adrian Pronk, 06.11.2012 01:15
readlink -f is GNU-specific. BSD readlink does not have that option. by Kara Brightwell, 03.06.2014 16:48
What'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. by Charles Duffy, 09.06.2014 03:37
The list of cases handled (Works for all versions) above has one item == when the file it. Can anyone appropriately complete that sentence fragment? by TomRoche, 31.10.2017 21:22
by far, the best by Vivick, 09.11.2017 20:49
What's with the long hash at the bottom? Is it a signature of some kind? What does it tell us? by mwfearnley, 29.03.2019 08:57
In fact, why is this Copyleft in the first place? Does the author ("user25866") want attribution, despite the answer being a community wiki? I don't think SO is a good place to dump a little snippet and then attach a license to it. To guard ownership and authenticate changes, a Github Gist would be much better. by mwfearnley, 29.03.2019 09:13
Is the if block necessary? The while condition is the same, and while will skip if the condition is false the first time. by Jacktose, 26.01.2021 00:44
Show remaining 10 comments
4
113

Short answer:

`dirname $0`

or (preferably):

$(dirname "$0")
03.12.2008 12:57
Comments
It won't work if you source the script. "source my/script.sh" by Arunprasad Rajkumar, 05.02.2014 07:34
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. by kqw, 03.01.2017 16:28
@vidstige: ${BASH_SOURCE[0]} instead of $0 will work with source my/script.sh by Timothy Jones, 27.09.2017 06:48
@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. by Mathieu CAROFF, 13.11.2018 18:43
2
116

You can use $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

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

12.09.2008 20:50
Comments
When I do ./foo/script, then $(dirname $BASH_SOURCE) is ./foo. by Till, 25.10.2010 17:06
@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. by purushothaman poovai, 17.12.2019 05:17
4
83

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?

12.02.2016 23:40
Comments
with your solution, invoking the script like ./script.sh shows . instead of the full directory path by Bruno Negrão Zica, 14.06.2016 18:27
There's no -f option for readlink on MacOS. Use stat instead. But still, it shows . if you are in 'this' dir. by Denis The Menace, 21.11.2016 09:55
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. by dragon788, 22.04.2019 15:59
You should add double quotes surrounding all the right hand side: DIR="$(dirname "$(readlink -f "$0")")" by hagello, 17.03.2020 11:22
5
81

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
07.11.2018 04:30
Comments
Or, more obscurely, on one line: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") by agc, 04.04.2019 11:55
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. by User9123, 25.03.2020 01:14
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. by User9123, 25.03.2020 01:32
@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. by Wang, 24.05.2020 12:23
The only reason is missing coreutils on mac. I am using SCRIPT=$(realpath "${BASH_SOURCE[0]}") + DIR=$(dirname "$SCRIPT"). by puchu, 29.08.2020 10:15
2
67

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:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

This way, you get an absolute, rather than a relative directory.

Since the script will be run in a separate Bash instance, there isn't any 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.

15.09.2008 19:55
Comments
You can do it all in one line like this: DIRECTORY=$(cd dirname $0 && pwd) by dogbane, 29.10.2008 08:38
This doesn't work if the script sources another script and you want to know the name of the latter. by reinierpost, 28.03.2014 13:10
1
39

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:

./script

/usr/bin/script

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.

13.09.2008 01:07
Comments
If you intend to disprove his comment, PROVE that a script CAN access where it's stored with a code example. by Richard Duerr, 18.11.2015 18:54
0
34

This gets the current working directory on Mac OS X v10.6.6 (Snow Leopard):

DIR=$(cd "$(dirname "$0")"; pwd)
30.12.2010 07:20
2
35
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
07.10.2010 17:34
Comments
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. by Patrick, 19.09.2013 03:07
As 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. by IMSoP, 23.09.2013 16:51
1
34
$(dirname "$(readlink -f "$BASH_SOURCE")")
27.07.2011 07:40
Comments
I prefer, $BASH_SOURCE over $0 , because it's explicit even for readers not well-versed in bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")") by blobmaster, 08.01.2020 13:04
2
27

This is Linux specific, but you could use:

SELF=$(readlink /proc/$$/fd/255)
16.09.2008 19:55
Comments
It'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.\ by Keith Thompson, 29.03.2015 00:17
Interactive shell != script. Anyway realpath ${BASH_SOURCE[0]}; would seem to be the best way to go. by Steve Baker, 06.04.2015 12:52
2
23

Here is a POSIX compliant one-liner:

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

# test
echo $SCRIPT_PATH
06.03.2013 19:07
Comments
I had success with this when running a script by itself or by using sudo, but not when calling source ./script.sh by Michael R, 17.04.2013 21:57
And it fails when cd is configured to print the new path name. by Aaron Digulla, 04.11.2013 13:45
1
19

The shortest and most elegant way to do this is:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

This would work on all platforms and is super clean.

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

20.06.2019 22:08
Comments
great clean solution, but this will not work if the file is symlinked. by ruuter, 26.09.2019 08:43
2
18

I tried all of these and none worked. One was very close, but it 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:

#!/bin/bash
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"`"
09.09.2010 07:19
Comments
Doesn't work if the script is being sourced from another script. by reinierpost, 28.03.2014 13:12
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). by hagello, 17.03.2020 11:20
2
16

For systems having GNU coreutils readlink (for example, Linux):

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

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

28.05.2014 07:16
Comments
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) by osirisgothra, 05.02.2015 00:07
I added second pair of quotes around dirname call. Needed if the directory path contains spaces. by user1338062, 13.05.2018 04:35
1
17

Here is the simple, correct way:

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

Explanation:

  • ${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.

19.02.2016 20:30
Comments
greadlink -f unfortunately doesn't work effectively when sourceing the script on Mac :( by Gabe Kopley, 30.04.2019 23:50
7
10

Try using:

real=$(realpath $(dirname $0))
06.02.2012 10:43
Comments
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? by Shou Ya, 28.08.2012 15:16
realpath is not a standard utility. by Steve Bennett, 13.05.2013 12:06
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. by Doug Richardson, 18.07.2014 15:53
The 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. by hagello, 13.04.2015 21:43
This solution "just gets the path of the directory where the symlink it stored", and that's what I actually want. PS: quoting still should be added. by Kostiantyn Ponomarenko, 11.03.2019 10:19
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" by Kostiantyn Ponomarenko, 11.03.2019 12:52
SCRIPT=`realpath $0` by Pedro Lobito, 05.06.2020 13:19
Show remaining 2 comments
2
16

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

else

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

fi
21.11.2012 03:57
Comments
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. by Notinlist, 18.11.2014 10:25
@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. by Neil Mayhew, 03.02.2016 22:08
5
16

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

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

This should still work in all the cases they listed.

This will prevent popd after a failed pushd. Thanks to konsolebox.

13.04.2010 22:12
Comments
This works perfectly to get the "real" dirname, rather than just the name of a symlink. Thank you! by Jay Taylor, 23.06.2010 20:32
Better SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; } by konsolebox, 03.07.2014 04:15
@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. by Fuwjax, 19.01.2015 20:03
@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. by konsolebox, 20.01.2015 19:21
All those pushd popd dangers could be avoided simply by dropping them and using cd + pwd enclosed in a command substitution instead. SCRIPT_DIR=$(...) by Amit Naidu, 27.05.2020 03:16
1
16
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`
13.09.2008 01:28
Comments
I haven't tested it across different systems. But this solution is the one that works right away at least on Ubuntu, for me! by Natus Drew, 26.05.2020 00:30
2
13

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

16.09.2011 19:05
Comments
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. by clacke, 31.01.2014 14:55
This is Perl-like! A coincidence? by Peter Mortensen, 20.01.2021 03:44
4
11

I've compared many of the answers given, and came 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"
21.04.2013 13:40
Comments
I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop. by Dan Dascalescu, 15.08.2015 11:29
@DanDascalescu Using the one-liner? Or the full code snippet at the bottom? And were you feeding it any tricky pathnames? by billyjmc, 19.08.2015 06:34
The one line plus another line to echo $resolved, I saved it as d, chmod +x d, ./d. by Dan Dascalescu, 20.08.2015 05:55
@DanDascalescu The first line in your script needs to be #!/bin/bash by billyjmc, 23.08.2015 05:07
2
11

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/she calls the script. This will always work.

14.10.2008 16:39
Comments
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. by Reinstate Monica Please, 13.01.2014 22:30
"Always"? Not at all. which being an external tool, you have no reason to believe it behaves identically to the parent shell. by Charles Duffy, 09.06.2014 03:42
2
11

These are short ways to get script information:

Folders and files:

    Script: "/tmp/src dir/test.sh"
    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
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    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 : test.sh
     Script-Name : test.sh

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

     Calling-Dir : /tmp/src dir/other

Also see: https://pastebin.com/J8KjxrPF

09.03.2018 12:56
Comments
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 by User8461, 12.05.2020 19:30
1
8

Hmm, if in the path, basename and dirname are just not going to cut it and walking the path is hard (what if the 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.

24.01.2009 17:01
Comments
I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop, instead of the actual directory I run the script from. by Dan Dascalescu, 15.08.2015 11:30
3
9

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 one argument. If argument has already absolute path, print it as it is, otherwise print $PWD variable + filename argument (without ./ prefix).

Related:

28.11.2013 12:05
Comments
Please explain more about the realpath function. by Chris, 27.03.2015 16:54
@Chris realpath function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD + filename (without ./ prefix). by kenorb, 27.03.2015 17:35
Your cross-compatible solution doesn’t work when the script is symlinked. by Jakub Jirutka, 08.09.2015 20:59
0
9

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: "$@"\n" ;} # Borrow a horrible signal name.

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

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

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

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

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

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

## 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"                           ;;
   esac
 done
 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 "$@"
fi
21.12.2013 17:47
0
9

How to obtain the full file path, full directory, and base filename of any script being run itself

For many cases, all you need to acquire is the full path to the script you just called. This can be easily accomplished using realpath. 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.

get_script_path.sh:

#!/bin/bash

FULL_PATH_TO_SCRIPT="$(realpath "$0")"

# You can then also get the full path to the directory, and the base
# filename, like this:
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

# Now print it all out
echo "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""
echo "SCRIPT_DIRECTORY    = \"$SCRIPT_DIRECTORY\""
echo "SCRIPT_FILENAME     = \"$SCRIPT_FILENAME\""

Example output:

~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.sh 
FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh"
SCRIPT_DIRECTORY    = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash"
SCRIPT_FILENAME     = "get_script_path.sh"

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

The code above is now part of my eRCaGuy_hello_world repo in this file here: bash/get_script_path.sh.

References:

  1. How to retrieve absolute path given relative
10.02.2020 19:46
3
5

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

thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath
10.04.2017 21:49
Comments
Note that there's no ned to resort to echo. Simply invoking dirname will print the directory name. And, in this case, invoking echo without properly quoting the variable will potentially produce different output, since it will squash whitespace. Overall, cleaner to simply write dirname "$(realpath "$0")" by William Pursell, 08.08.2017 17:46
You are correct that this can be compressed into a single line. The breakdown into separate variables is for illustrative / self documenting purposes. The echo line is just there as p.o.c. by BuvinJ, 08.08.2017 19:05
I'm pretty sure it does (if I remember correctly). Note that different nix distros and contexts like you bring up (and say Linux, vs Mac, vs Cygwin...bash vs dash...) handle all of this somewhat differently. Hence the reason there are SOO many answers on this thread! You'll need to test your use case to confirm. I wish there were a solid, cross platform/context answer to this simple question! by BuvinJ, 19.04.2019 13:40
4
7

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.

08.10.2013 14:24
Comments
You 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? by konsolebox, 02.07.2014 17:21
dirname and basename are POSIX standardized, so why avoid using them? Links: dirname, basename by myrdd, 29.10.2018 09:18
Preventing two extra process forks and sticking to shell built-ins may be one reason why. by Amit Naidu, 27.05.2020 03:37
You can call me purist - I have a somewhat similar version that doesn't involve subshells or forking anything: BIN=${0/#[!\/]/"$PWD/${0:0:1}"}; DIR=${BIN%/*}. First replace anything not starting by / (i.e. a relative path) with $PWD/ (we also have to append the first character we matched in the substitution). Then get the directory. You can finally cd $DIR if you wish, or use it in relative paths. by Thomas Guyot-Sionnest, 23.12.2020 20:38
1
4

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

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
14.04.2009 16:00
Comments
this is giving me the directory with the last entry stripped off, i.e., the path to the container of the container of the script. by tim, 28.03.2015 15:17
0
7

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_PATH="${0}"
RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"

echo "RELATIVE_PATH->${RELATIVE_PATH}<-"
echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"
echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"
echo "FULL_PATH->${FULL_PATH}<-"

The output will be something like this:

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

$0 is the name of the script itself

4.4. Special Variable Types

An example: LozanoMatheus/get_script_paths.sh

26.08.2019 15:01
0
5

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"`"
13.10.2010 07:42
0
5

None of these other answers 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/:`"

It is not pretty, but it gets the job done.

22.09.2010 23:22
1
3

The top response does not work in all cases...

As I had problems with the BASH_SOURCE with the included 'cd' approach on some very fresh and also on less fresh installed Ubuntu 16.04 (Xenial Xerus) systems when invoking the shell script by means of "sh my_script.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 processes - but when seeing the multi-line scripting for resolving the true object it won't be that bad either having it solve in a single binary usage.)

So let’s see one example of those alternate solution for the described task of querying the true absolute path to a certain file:

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`

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

SCRIPT=`realpath -s "$0"`
SCRIPTPATH=`dirname "$SCRIPT"`

Indeed, if you don’t 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?

25.10.2019 16:32
Comments
This question is bash specific. If you invoke a script with sh, the shell might be something else, such as zsh or dash. by neu242, 04.12.2019 13:08
0
3

I usually use:

dirname $(which $BASH_SOURCE)
17.05.2019 12:09
0
3

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

#!/bin/bash
echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/my_script.sh &

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

/home/puchuu/.xprofile
/home/puchuu/.xprofile
-bash
/home/puchuu/.xprofile

So please use BASH_SOURCE instead.

04.02.2017 08:56
0
4

None 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 and then strip just that character off:

dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd && echo x)"
dir="${dir%x}"

This protects against two very common situations: Accidents and sabotage. A script shouldn't fail in unpredictable ways just because someone, somewhere, did a mkdir $'\n'.

15.04.2021 08:48
0
2

Here's a command that works under either Bash or zsh, and whether executed stand-alone 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}"
ok

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

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

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

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/test.sh
fpath=%x
echo "${(%)fpath}"

% source apath/test.sh
apath/test.sh

% cd apath
% source test.sh
test.sh

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 test.sh
echo "${(%):-%x}"

% source test.sh
test.sh

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/test.sh
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "${(%):-%x}"

% source apath/test.sh
apath
apath

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

22.05.2019 19:57
0
2

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 $ . ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ cd

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

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

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

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

test.sh

#!/usr/bin/env bash

# snagged from: https://stackoverflow.com/a/51264222/26510
function toAbsPath {
    local target
    target="$1"

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

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

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

SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"
08.10.2019 12:37
0
2

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"))";
my_path="$dirn/$exec_dir";
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 it is the only way I know of doing this properly.

01.06.2018 02:52
0
1

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/test_script.sh or bash test1/test2/test_script.sh.

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

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

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

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
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 associated FUNCNAME.

BASH_SOURCE

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]}.

FUNCNAME

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 doesn't have any 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]

31.08.2014 22:10
1
-3

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.

DIR=$(/usr/bin/pwd)
12.02.2009 15:06
Comments
How does this differ from other answers? by mmmmmm, 20.01.2015 12:18
0
2

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

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

echo $baseDir
26.12.2019 08:34
5
-1
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/'
    then
        RESULT=`dirname "$script_invoke_path"`
    else
        RESULT=`dirname "$cwd/$script_invoke_path"`
    fi
} # <<fold
29.11.2009 11:44
Comments
sorry I'm a bit of a bash scrip noob, do I call this function by just typing getScriptAbsoluteDir or local currdir='getScriptAbsoluteDir'? by Jiaaro, 30.11.2009 16:04
-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. by Charles Duffy, 09.06.2014 03:41
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. by syntaxerror, 09.09.2014 23:31
An explanation would be in order. by Peter Mortensen, 20.01.2021 03:37
@PeterMortensen why should I? I pasted a solution that worked for me for years and I got spat upon on things that are completely irrelevant. They don't like the function they can take it away. They don't like the hack they can remove it. by Stefano Borini, 21.01.2021 10:14
0
2

I tried the followings with 3 different executions.

echo $(realpath $_)

. application         # /correct/path/to/dir or /path/to/temporary_dir
bash application      # /path/to/bash
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $(dirname $0))

. application         # failed with `realpath: missing operand`
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $BASH_SOURCE)

$BASH_SOURCE is basically the same with ${BASH_SOURCE[0]}.

. application         # /correct/path/to/dir
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

Only $(realpath $BASH_SOURCE) seems to be reliable.

20.01.2021 03:20
0
1

One advantage of this method is that it doesn't involve anything outside Bash itself and does not fork any subshell neither.

First, use pattern substitution to replace anything not starting with / (i.e., a relative path) with $PWD/. Since we use a substitution to match the first character of $0, we also have to append it back (${0:0:1} in the substitution).

Now we have a full path to the script; we can get the directory by removing the last / and anything the follows (i.e., the script name). That directory can then be used in cd or as a prefix to other paths relative to your script.

#!/bin/bash

BIN=${0/#[!\/]/"$PWD/${0:0:1}"}
DIR=${BIN%/*}

cd "$DIR"

If your script may be sourced rather than executed, you can of course replace $0 with ${BASH_SOURCE[0]}, such as:

BIN=${BASH_SOURCE[0]/#[!\/]/"$PWD/${BASH_SOURCE[0]:0:1}"}

This will work for executable scripts too. It's longer, but more polyvalent.

23.12.2020 20:55