Categories
bash directory

How do I get the directory where a Bash script is located from within the script itself?

5788

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

  • 87

    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

    Sep 24, 2012 at 12:15

  • 92

    @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

    Mar 28, 2013 at 8:14

  • 34

    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 🙂

    Feb 5, 2015 at 0:12

  • 73

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

    Jan 30, 2016 at 2:22

  • "${PWD%/}/application"

    – sksoumik

    Feb 19 at 3:20

7612

#!/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'

10

  • 36

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

    Oct 19, 2011 at 15:54

  • 23

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

    Feb 3, 2013 at 2:33

  • 234

    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

    Jun 9, 2015 at 19:32


  • 194

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

    Oct 28, 2015 at 23:38


  • 3

    @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

    Apr 18 at 4:12


1097

Use dirname "$0":

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

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

9

  • 30

    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

    Oct 23, 2008 at 20:15

  • 13

    @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

    Feb 5, 2010 at 12:18

  • 113

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

    Jun 11, 2010 at 12:56

  • 40

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

    Jan 23, 2011 at 10:30


  • 12

    @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

    Jan 24, 2011 at 12:55

622

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

13

  • 16

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

    – T.L

    Jan 11, 2012 at 9:14

  • 49

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

    – Catskul

    Sep 17, 2013 at 19:40

  • 14

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

    – cucu8

    Nov 26, 2014 at 9:29

  • 11

    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

    Jan 14, 2016 at 20:16


  • 7

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

    – svvac

    Jun 30, 2016 at 16:52