6278

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
5
  • 92
    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}".
    – l0b0
    Commented Sep 24, 2012 at 12:15
  • 97
    @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
    Commented Mar 28, 2013 at 8:14
  • 38
    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 :) Commented Feb 5, 2015 at 0:12
  • 86
    I stronly suggest to read this Bash FAQ about the subject. Commented Jan 30, 2016 at 2:22
  • @osirisgothra Anyone who thinks the solution to hazardous characters in filenames is not to write robust scripts, but is instead to prevent people (who may at some point create a hazardous name) from ever accessing the system, is going to have a difficult time.
    – Walf
    Commented May 3 at 4:15

77 Answers 77

8123
#!/usr/bin/env 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:

#!/usr/bin/env bash

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

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -L "$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'
16
  • 39
    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)". Commented Oct 19, 2011 at 15:54
  • 25
    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 )"
    – user716468
    Commented Feb 3, 2013 at 2:33
  • 255
    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
    – tvlooy
    Commented Jun 9, 2015 at 19:32
  • 214
    @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. Commented Oct 28, 2015 at 23:38
  • 20
    @tvlooy dirname "$(readlink -f "$0")" fails though in the case where the script is sourced, eg: /bin/bash -c . script.sh... use $(dirname "$(readlink -f "${BASH_SOURCE[0]}")") instead
    – tekumara
    Commented Apr 18, 2022 at 4:12
1262

Use dirname "$0":

test.sh:

#!/usr/bin/env bash

echo "The script you are running has:"
echo "basename: [$(basename "$0")]"
echo "dirname : [$(dirname "$0")]"
echo "pwd     : [$(pwd)]"

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

[~]$ pwd
/home/matt
[~]$ ./test.sh
The script you are running has:
basename: [test.sh]
dirname : [/home/matt]
pwd     : [/home/matt]

[~]$ cd /tmp
[~/tmp]$ ~/test.sh
The script you are running has:
basename: [test.sh]
dirname : [/home/matt]
pwd     : [/tmp]
10
  • 35
    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.
    – Darron
    Commented Oct 23, 2008 at 20:15
  • 14
    @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.
    – D.Shawley
    Commented Feb 5, 2010 at 12:18
  • 126
    @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. Commented Jun 11, 2010 at 12:56
  • 51
    +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?) Commented Jan 23, 2011 at 10:30
  • 13
    @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.
    – matt b
    Commented Jan 24, 2011 at 12:55
688

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:

#!/usr/bin/env 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

There is however one case where this doesn't work, when the script is sourced (instead of executed) in bash:

>>>$ cd /tmp
>>>$ . ~/whatdir.sh  
pwd: /tmp
$0: bash
basename: bash
dirname: .
dirname/readlink: /tmp
14
  • 20
    readlink will not availabe in some platform in default installation. Try to avoid using it if you can
    – T.L
    Commented Jan 11, 2012 at 9:14
  • 55
    be careful to quote everything to avoid whitespace issues: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
    – Catskul
    Commented Sep 17, 2013 at 19:40
  • 16
    In OSX Yosemite 10.10.1 -f is not recognised as an option to readlink. Using stat -f instead does the job. Thanks
    – cucu8
    Commented Nov 26, 2014 at 9:29
  • 12
    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]}`
    – robert
    Commented Jan 14, 2016 at 20:16
  • 9
    Note that $0 doesn't work if the file is sourced. You get -bash instead of the script name.
    – svvac
    Commented Jun 30, 2016 at 16:52
205
pushd . > '/dev/null';
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";

while [ -h "$SCRIPT_PATH" ];
do
    cd "$( dirname -- "$SCRIPT_PATH"; )";
    SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";
done

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]:-$0}";

while [ -h "$SCRIPT_PATH" ];
do
    cd "$( dirname -- "$SCRIPT_PATH"; )";
    SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";
done

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.

7
  • 4
    Nice! Could be made shorter replacing "pushd[...] popd /dev/null" by SCRIPT_PATH=readlink -f $(dirname "${VIRTUAL_ENV}");
    – Bite code
    Commented Nov 29, 2009 at 11:34
  • 1
    This is by far the most "stable" version I've seen. Thank you! Commented Jan 26, 2010 at 8:19
  • 5
    And instead of using pushd ...; would not it be better to use $(cd dirname "${SCRIPT_PATH}" && pwd)? But anyway great script!
    – ovanes
    Commented Aug 18, 2010 at 10:16
  • 7
    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) Commented Nov 6, 2012 at 1:15
  • 8
    readlink -f is GNU-specific. BSD readlink does not have that option. Commented Jun 3, 2014 at 16:48
149

Here is an easy-to-remember script:

DIR="$( dirname -- "${BASH_SOURCE[0]}"; )";   # Get the directory name
DIR="$( realpath -e -- "$DIR"; )";    # Resolve its full path if need be
9
  • 14
    Or, more obscurely, on one line: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
    – agc
    Commented Apr 4, 2019 at 11:55
  • 11
    Why isn't this the accepted answer? Is there any difference using realpath from resolving "manually" with a loop of readlink? Even the readlink man page says Note realpath(1) is the preferred command to use for canonicalization functionality.
    – User9123
    Commented Mar 25, 2020 at 1:14
  • 7
    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.
    – User9123
    Commented Mar 25, 2020 at 1:32
  • 2
    @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.
    – Wang
    Commented May 24, 2020 at 12:23
  • 1
    My realpath (and man7.org/linux/man-pages/man1/realpath.1.html ) don't have the -f option; is that supposed to be -e @vaeVictis ? (That user added it.) Commented Oct 20, 2022 at 16:23
145

You can use $BASH_SOURCE:

#!/usr/bin/env bash

scriptdir="$( dirname -- "$BASH_SOURCE"; )";

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

3
  • 19
    When I do ./foo/script, then $(dirname $BASH_SOURCE) is ./foo.
    – Till
    Commented Oct 25, 2010 at 17:06
  • 5
    @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. Commented Dec 17, 2019 at 5:17
  • BASH_SOURCE is an array, so I'm not sure this is right. All the other answers use BASH_SOURCE[0] which seems more plausible.
    – Timmmm
    Commented Jul 1 at 7:46
126

Short answer:

"`dirname -- "$0";`"

or (preferably):

"$( dirname -- "$0"; )"
7
  • 24
    It won't work if you source the script. "source my/script.sh" Commented Feb 5, 2014 at 7: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.
    – the
    Commented Jan 3, 2017 at 16:28
  • 21
    @vidstige: ${BASH_SOURCE[0]} instead of $0 will work with source my/script.sh Commented Sep 27, 2017 at 6:48
  • 2
    @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. Commented Nov 13, 2018 at 18:43
  • 1
    @NamGVU Yes, that's right. This question is about Bash, though Commented Jan 10, 2023 at 8:25
122

This should do it:

DIR="$(dirname "$(realpath "$0")")"

This works with symlinks and spaces in path.

Please see the man pages for dirname and realpath.

Please add a comment on how to support MacOS. I'm sorry I can verify it.

8
  • 10
    with your solution, invoking the script like ./script.sh shows . instead of the full directory path Commented Jun 14, 2016 at 18:27
  • 6
    There's no -f option for readlink on MacOS. Use stat instead. But still, it shows . if you are in 'this' dir. Commented Nov 21, 2016 at 9:55
  • 3
    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.
    – dragon788
    Commented Apr 22, 2019 at 15:59
  • 1
    @MarkGates that's wrong, I got realpath: command not found on vanilla macOS, I suppose you have installed some homebrew package to get it working on your mac
    – knocte
    Commented Nov 4, 2021 at 20:08
  • 2
    $BASH_SOURCE works properly in my .bashrc while $0 does not Commented Jan 5, 2022 at 13:05
76

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:

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

2
  • 12
    You can do it all in one line like this: DIRECTORY=$(cd dirname $0 && pwd)
    – dogbane
    Commented Oct 29, 2008 at 8:38
  • 1
    This doesn't work if the script sources another script and you want to know the name of the latter. Commented Mar 28, 2014 at 13:10
43

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

DIR=$(cd "$(dirname "$0")"; pwd)
1
  • so this doesn't work on Linux?
    – knocte
    Commented Nov 4, 2021 at 20:11
42
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
2
  • 7
    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.
    – IMSoP
    Commented Sep 23, 2013 at 16:51
  • @IMSoP, wrong. "many of the previous answers" explains that pwd returns the current directory, in this case the directory changed before the call. As for $0, in most cases it is desired to use $0 instead of $BASH_SOURCE, eg. if script sourcing a library that needs do determine full path of the script (not library).
    – Alek
    Commented Jan 19, 2023 at 3:00
41

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.

3
  • 9
    If you intend to disprove his comment, PROVE that a script CAN access where it's stored with a code example. Commented Nov 18, 2015 at 18:54
  • Did you tried it ? $0 does have the full path information for third case.
    – Alek
    Commented Jan 19, 2023 at 3:03
  • To get the full path information inside a Bash script of the directory containing it, command mydir=$(realpath $0). I've tested that it works in all the situations you describe. Commented Apr 26 at 10:01
38
$(dirname "$(readlink -f "$BASH_SOURCE")")
2
  • I prefer, $BASH_SOURCE over $0 , because it's explicit even for readers not well-versed in bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
    – blobmaster
    Commented Jan 8, 2020 at 13:04
  • Also, $BASH_SOURCE works while $0 does not in case of my .bashrc (where both symlink AND sourcing is also used) Commented Jan 5, 2022 at 13:03
30

This is Linux specific, but you could use:

SELF=$(readlink /proc/$$/fd/255)
2
  • 1
    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.\ Commented Mar 29, 2015 at 0:17
  • 3
    Interactive shell != script. Anyway realpath ${BASH_SOURCE[0]}; would seem to be the best way to go. Commented Apr 6, 2015 at 12:52
27

Here is a POSIX compliant one-liner:

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

# test
echo $SCRIPT_PATH
2
  • 5
    I had success with this when running a script by itself or by using sudo, but not when calling source ./script.sh
    – Michael R
    Commented Apr 17, 2013 at 21:57
  • And it fails when cd is configured to print the new path name. Commented Nov 4, 2013 at 13:45
27

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?".

1
  • 1
    great clean solution, but this will not work if the file is symlinked.
    – ruuter
    Commented Sep 26, 2019 at 8:43
26

For Python, see my other answer here.

For Bash, see below:

Summary:

FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"

# OR, if you do NOT need it to work for **sourced** scripts too:
# FULL_PATH_TO_SCRIPT="$(realpath "$0")"

# OR, depending on which path you want, in case of nested `source` calls
# FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[0]}")"

# OR, add `-s` to NOT expand symlinks in the path:
# FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

Details:

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

...even when the called script is called from within another bash function or script, or when nested sourcing is being used!

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 (for the latest version of this script, see get_script_path.sh in my eRCaGuy_hello_world repo):

#!/bin/bash

# A. Obtain the full path, and expand (walk down) symbolic links
# A.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.
# FULL_PATH_TO_SCRIPT="$(realpath "$0")"
# A.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even
# if the script is called from within another bash function!
# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use
# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.
FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"
# B.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.
# FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "$0")"
# B.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even
# if the script is called from within another bash function!
# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use
# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.
FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "${BASH_SOURCE[-1]}")"

# 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\""

IMPORTANT note on nested source calls: if "${BASH_SOURCE[-1]}" above doesn't give you quite what you want, try using "${BASH_SOURCE[0]}" instead. The first (0) index gives you the first entry in the array, and the last (-1) index gives you the last last entry in the array. Depending on what it is you're after, you may actually want the first entry. I discovered this to be the case when I sourced ~/.bashrc with . ~/.bashrc, which sourced ~/.bash_aliases with . ~/.bash_aliases, and I wanted the realpath (with expanded symlinks) to the ~/.bash_aliases file, NOT to the ~/.bashrc file. Since these are nested source calls, using "${BASH_SOURCE[0]}" gave me what I wanted: the expanded path to ~/.bash_aliases! Using "${BASH_SOURCE[-1]}", however, gave me what I did not want: the expanded path to ~/.bashrc.

Example command and output:

  1. Running the script:
    ~/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"
    
  2. Sourcing the script with . get_script_path.sh or source get_script_path.sh (the result is the exact same as above because I used "${BASH_SOURCE[-1]}" in the script instead of "$0"):
    ~/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"
    

If you use "$0" in the script instead of "${BASH_SOURCE[-1]}", you'll get the same output as above when running the script, but this undesired output instead when sourcing the script:

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

And, apparently if you use "$BASH_SOURCE" instead of "${BASH_SOURCE[-1]}", it will not work if the script is called from within another bash function. So, using "${BASH_SOURCE[-1]}" is therefore the best way to do it, as it solves both of these problems! See the references below.

Difference between realpath and realpath -s:

Note that realpath also successfully walks down symbolic links to determine and point to their targets rather than pointing to the symbolic link. If you do NOT want this behavior (sometimes I don't), then add -s to the realpath command above, making that line look like this instead:

# Obtain the full path, but do NOT expand (walk down) symbolic links; in
# other words: **keep** the symlinks as part of the path!
FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

This way, symbolic links are NOT expanded. Rather, they are left as-is, as symbolic links in the full path.

The code above is now part of my eRCaGuy_hello_world repo in this file here: bash/get_script_path.sh. Reference and run this file for full examples both with and withOUT symlinks in the paths. See the bottom of the file for example output in both cases.

References:

  1. How to retrieve absolute path given relative
  2. taught me about the BASH_SOURCE variable: Unix & Linux: determining path to sourced shell script
  3. taught me that BASH_SOURCE is actually an array, and we want the last element from it for it to work as expected inside a function (hence why I used "${BASH_SOURCE[-1]}" in my code here): Unix & Linux: determining path to sourced shell script
  4. man bash --> search for BASH_SOURCE:

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

See also:

  1. My answer for Python: How do I get the path and name of the python file that is currently executing?
  2. [my answer] Unix & Linux: determining path to sourced shell script
3
  • 2
    What's the difference between ${BASH_SOURCE[-1]} and ${BASH_SOURCE[0]} ? I know -1 retrieves the last element from the array and 0 retrieves the first one but in which case do I want to use one over the other ?
    – Elie G.
    Commented Apr 13, 2022 at 14:26
  • @ElieG., See my IMPORTANT note on nested source calls section in the answer. It has to do with nested sourcing, when one script which you source sources another script. Commented Apr 13, 2022 at 15:20
  • @ElieG., I think so. Also, I didn't try it, but in my case I think index 1 would have given me the same result as -1 since I believe the array only had 2 elements in it, so that would have been the last element in both cases. Commented Apr 13, 2022 at 15:56
22
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`
1
  • 1
    I haven't tested it across different systems. But this solution is the one that works right away at least on Ubuntu, for me!
    – Natus Drew
    Commented May 26, 2020 at 0:30
20

Try using:

real=$(realpath "$(dirname "$0")")
7
  • 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?
    – shouya
    Commented Aug 28, 2012 at 15:16
  • 7
    realpath is not a standard utility. Commented May 13, 2013 at 12:06
  • 5
    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. Commented Jul 18, 2014 at 15:53
  • 5
    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.
    – hagello
    Commented Apr 13, 2015 at 21:43
  • 4
    dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" Commented Mar 11, 2019 at 12:52
19

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.

1
  • 2
    greadlink -f unfortunately doesn't work effectively when sourceing the script on Mac :( Commented Apr 30, 2019 at 23:50
19

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"`"
2
  • 4
    Doesn't work if the script is being sourced from another script. Commented Mar 28, 2014 at 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).
    – hagello
    Commented Mar 17, 2020 at 11:20
18

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.

5
  • This works perfectly to get the "real" dirname, rather than just the name of a symlink. Thank you!
    – Jay Taylor
    Commented Jun 23, 2010 at 20:32
  • 1
    Better SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
    – konsolebox
    Commented Jul 3, 2014 at 4: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.
    – Fuwjax
    Commented Jan 19, 2015 at 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.
    – konsolebox
    Commented Jan 20, 2015 at 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=$(...)
    – Amit Naidu
    Commented May 27, 2020 at 3:16
17

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
2
  • 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
    Commented Nov 18, 2014 at 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. Commented Feb 3, 2016 at 22:08
17

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.

1
  • 4
    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) Commented Feb 5, 2015 at 0:07
14

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

2
  • 4
    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.
    – clacke
    Commented Jan 31, 2014 at 14:55
  • This is Perl-like! A coincidence? Commented Jan 20, 2021 at 3:44
14

Incredible how simple can be, no matter how you call the script:

#!/bin/bash
#

the_source=$(readlink -f ${BASH_SOURCE[0]})
the_dirname=$(dirname ${the_source})

echo "the_source: ${the_source}"
echo "the_dirname: ${the_dirname}"

Run from anywhere:

user@computer:~/Downloads/temp$ ./test.sh 

Output:

the_source: /home/user/Downloads/temp/test.sh
the_dirname: /home/user/Downloads/temp
1
  • Upvoting this answer because it tells in which directory script really is, no mater if calling it from symlink or original script. It is especially useful when your script is just a symlink in .bin directory created by npm/yarn and you need to now original script location to relatively reference static resources bundled in your npm package... Commented Jul 19, 2023 at 15:41
13

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

0
13

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.

2
  • 5
    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. Commented Jan 13, 2014 at 22:30
  • 2
    "Always"? Not at all. which being an external tool, you have no reason to believe it behaves identically to the parent shell. Commented Jun 9, 2014 at 3:42
10

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"
4
  • I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop. Commented Aug 15, 2015 at 11:29
  • @DanDascalescu Using the one-liner? Or the full code snippet at the bottom? And were you feeding it any tricky pathnames?
    – billyjmc
    Commented Aug 19, 2015 at 6:34
  • The one line plus another line to echo $resolved, I saved it as d, chmod +x d, ./d. Commented Aug 20, 2015 at 5:55
  • @DanDascalescu The first line in your script needs to be #!/bin/bash
    – billyjmc
    Commented Aug 23, 2015 at 5:07
10

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:

2
  • 2
    @Chris realpath function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD + filename (without ./ prefix).
    – kenorb
    Commented Mar 27, 2015 at 17:35
  • 2
    Your cross-compatible solution doesn’t work when the script is symlinked. Commented Sep 8, 2015 at 20:59

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