773

I'm writing a Bash script. I need the current working directory to always be the directory that the script is located in.

The default behavior is that the current working directory in the script is that of the shell from which I run it, but I do not want this behavior.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jameshfisher
  • 34,029
  • 31
  • 121
  • 167
  • Have you considered putting a wrapper script somewhere like /usr/bin to cd into the (hardcoded) proper directory and then execute your script? – Dagg Nabbit Jul 28 '10 at 01:02
  • 2
    Why do you need the directory of the script? There's probably a better way to solve the underlying problem. – bstpierre Jul 28 '10 at 03:59
  • 18
    I'd just like to point out that the behavior you call "obviously undesirable" is in fact entirely necessary -- if I run `myscript path/to/file` I expect the script to evaluate path/to/file relative to MY current directory, not whatever directory the script happens to be located in. Also, what would you have happen for a script run with `ssh remotehost bash < ./myscript` as the BASH FAQ mentions? – Gordon Davisson Jul 28 '10 at 04:54
  • possible duplicate of [Can a Bash script tell what directory it's stored in?](http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in) – kenorb Apr 24 '15 at 02:23
  • 4
    `cd "${BASH_SOURCE%/*}" || exit` – caw Jul 07 '17 at 23:48

13 Answers13

920
#!/bin/bash
cd "$(dirname "$0")"
ndim
  • 35,870
  • 12
  • 47
  • 57
  • 9
    dirname returns '.' when using bash under Windows. So, Paul's answer is better. – Tvaroh May 28 '13 at 10:10
  • 9
    Also returns '.' in Mac OSX – Ben Clayton Jul 11 '13 at 18:16
  • 4
    It's worth noting that things can break if a symbolic link makes up part of `$0`. In your script you may expect, for example, `../../` to refer to the directory two levels above the script's location, but this isn't necessarily the case if symbolic links are in play. – Rag Dec 24 '13 at 08:51
  • 1
    Returns '.' on Ubuntu 14. – Jason D May 01 '14 at 21:03
  • 33
    If you called the script as `./script`, `.` is the correct directory, and changing to `.` it will also end up in the very directory where `script` is located, i.e. in the current working directory. – ndim Aug 27 '14 at 20:10
  • 1
    Perhaps `cd "$(dirname -- "$0")"` is somewhat better in handling situation where `$0` starts with a minus sign. – skyking May 18 '16 at 08:31
  • as a note, i would consider using `pushd` / `popd` instead of `cd`. if your script is being executed from the terminal like normal it doesn't matter if you change the pwd because it runs as a child process, but there's always the off-chance that someone is `source`ing the script, or possibly some other tool will execute it in the same process. – orion elenzil Jun 01 '16 at 22:14
  • 12
    If you run the script from the current directory like so `bash script.sh`, then the value of `$0` is `script.sh`. The only way the `cd` command will "work" for you is because you don't care about failed commands. If you were to use `set -o errexit` (aka: `set -e`) to ensure that your script doesn't blow past failed commands, this would NOT work because `cd script.sh` is an error. The reliable [bash specific] way is `cd "$(dirname ${BASH_SOURCE[0]})"` – Bruno Bronosky Dec 18 '16 at 18:55
  • 3
    @BrunoBronosky: `dirname script.sh` prints `.` and exits with exit code 0. Therefore `cd .` is executed, also exiting with exit code 0. I cannot fathom where a failing command would appear. – ndim Dec 20 '16 at 21:26
  • Not sure what the first two comments (by Tvaroh and Ben Clayton) are trying to say, but as far as I can see, it's fine that dirname returns '.'. This works fine in Mac OS X for all cases, and Paul's answer (which also returns '.' when it works) doesn't work for 'bash script.sh' in the current directory. – njlarsson May 07 '17 at 09:05
  • 1
    @dimpiax: Using backticks instead of proper quoting fails for directories whose name contains spaces. I reverted your edit adding a backtick based alternative. – ndim May 23 '17 at 21:56
  • The quotation marks seem messed up. Shouldn't you escape the inner ones? `cd "$(dirname \"$0\")"` – Lyuboslav Apr 13 '18 at 13:42
  • 2
    @lyuboslav The quotation marks are fine. They are not passed to some subshell as a string to be parsed by that subshell. Those inner quotes are used by the outer shell to run the `dirname` command with a single parameter without interpreting the value of `$0` in any way. Then the outer quotes make sure the output of `dirname` is used as a single parameter to `cd` without interpreting the string in any way. – ndim Apr 20 '18 at 10:47
  • Just thought it might be worth mentioning, you can use "pushd" instead of cd, which then allows you to "popd" after the script completes to return to where they launched the script. – Aerophilic Oct 04 '18 at 03:17
  • 1
    @Aerophilic: A script is run as a separate process, and the current working directory of one process does not affect the current working directory of any other process at all. – ndim Oct 20 '18 at 22:18
  • 1
    @lyuboslavkanev, ...perhaps you were thinking of backtick-based command substitution syntax, where escaping inner quotes is needed; that's not the case for `$(...)`. – Charles Duffy Apr 03 '19 at 15:08
  • Declare dirname = $(pwd) to route to current directory – Sanket Pandya Jul 05 '19 at 13:22
  • 1
    @SanketPandya That makes no sense. What are you trying to achieve? – ndim Jul 07 '19 at 21:30
  • 1
    @Aerophilic If you're doing only one `cd`, you can return by doing `cd -` after the script completes. – Lyuboslav Oct 31 '19 at 12:14
386

The following also works:

cd "${0%/*}"

The syntax is thoroughly described in this StackOverflow answer.

Richard
  • 56,349
  • 34
  • 180
  • 251
Paul Schulz
  • 3,925
  • 1
  • 12
  • 2
247

Try the following simple one-liners:


For all UNIX/OSX/Linux

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

Bash

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

Note: A double dash (--) is used in commands to signify the end of command options, so files containing dashes or other special characters won't break the command.

Note: In Bash, use ${BASH_SOURCE[0]} in favor of $0, otherwise the path can break when sourcing it (source/.).


*For Linux, Mac and other BSD:

cd "$(dirname "$(realpath -- "$0")")";

Note: realpath should be installed in the most popular Linux distribution by default (like Ubuntu), but in some it can be missing, so you have to install it.

Note: If you're using Bash, use ${BASH_SOURCE[0]} in favor of $0, otherwise the path can break when sourcing it (source/.).

Otherwise you could try something like that (it will use the first existing tool):

cd "$(dirname "$(readlink -f -- "$0" || realpath -- "$0")")"

For Linux specific:

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

*Using GNU readlink on BSD/Mac:

cd "$(dirname "$(greadlink -f -- "$0")")"

Note: You need to have coreutils installed (e.g. 1. Install Homebrew, 2. brew install coreutils).


In bash

In bash you can use Parameter Expansions to achieve that, like:

cd "${0%/*}"

but it doesn't work if the script is run from the same directory.

Alternatively you can define the following function in bash:

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

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

or here is the version taken from Debian .bashrc file:

function realpath()
{
    f=$@
    if [ -d "$f" ]; then
        base=""
        dir="$f"
    else
        base="/$(basename -- "$f")"
        dir="$(dirname -- "$f")"
    fi
    dir="$(cd -- "$dir" && /bin/pwd)"
    echo "$dir$base"
}

Related:

See also:

How can I get the behavior of GNU's readlink -f on a Mac?

FWDekker
  • 1,823
  • 2
  • 20
  • 26
kenorb
  • 155,785
  • 88
  • 678
  • 743
  • 6
    a much better answer than the popular ones because it resolves syslinks, and accounts for different OS's. Thanks! – Dennis Oct 03 '13 at 17:08
  • Thanks for this answer. The top one worked on my Mac... but what does the -- switch do in the `cp` command, @kenorb? – TobyG Jun 12 '14 at 07:18
  • 3
    A double dash (`--`) is used in commands to signify the end of command options, so files containing dashes or other special characters won't break the command. Try e.g. create the file via `touch "-test"` and `touch -- -test`, then remove the file via `rm "-test"` and `rm -- -test`, and see the difference. – kenorb Jun 12 '14 at 08:15
  • 1
    I'd remove my upvote if I could: realpath is deprecated http://unix.stackexchange.com/questions/136494/whats-the-difference-between-realpath-and-readlink-f – lsh Nov 05 '15 at 13:03
  • 2
    @lsh It says that only Debian `realpath` package is deprecated, not GNU `realpath`. If you think it's not clear, you can suggest an edit. – kenorb Nov 05 '15 at 13:55
  • `realpath` is not available in Mac OS Catalina – forresthopkinsa May 15 '20 at 19:39
  • 2
    Shouldn't all the examples like `cd "$(dirname "$(realpath "$0")")"` also use `--` before the parameter, like `cd "$(dirname -- "$(realpath -- "$0")")"`? – ntninja Jan 17 '21 at 09:03
  • Just to be clear : the first line doesn't actually answer the question. It seems to only define `$dir`. You'd have to use `cd $dir` afterwards, right? – Eric Duminil Dec 18 '21 at 15:41
  • @EricDuminil If you only want to cd to the script's directory, `cd -P -- "$(dirname -- "$0")"` suffices. If you want to use that path elsewhere, then you have to include the `pwd -P` part so that you get an absolute path without symlinks. – FWDekker Feb 17 '23 at 12:35
  • @FWDekker: What I meant is that `dir=$(cd -P -- "$(dirname -- "$0")" && pwd -P)` doesn't set the current working directory to the directory of the script (the original question). It simply defines a variable, without changing the working directory. – Eric Duminil Feb 17 '23 at 13:05
  • 1
    @EricDuminil Yes, I also found that a bit confusing in the answer. The actual answer to the question would be to execute the first line and then do `cd "$dir"` afterwards (the `""` are important in case the path contains whitespace), as you suggested. Alternatively, you can only execute `cd -P -- "$(dirname -- "$0")"`, which is much shorter and achieves the same thing, as far as I am aware. – FWDekker Feb 17 '23 at 13:18
  • 1
    @FWDekker: I typically don't use whitespace in file names, but it's still a good habit to use double quotes. Thanks. – Eric Duminil Feb 17 '23 at 13:41
95
cd "$(dirname "${BASH_SOURCE[0]}")"

It's easy. It works.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Dan Moulding
  • 211,373
  • 23
  • 97
  • 98
20

The accepted answer works well for scripts that have not been symlinked elsewhere, such as into $PATH.

#!/bin/bash
cd "$(dirname "$0")"

However if the script is run via a symlink,

ln -sv ~/project/script.sh ~/bin/; 
~/bin/script.sh

This will cd into the ~/bin/ directory and not the ~/project/ directory, which will probably break your script if the purpose of the cd is to include dependencies relative to ~/project/

The symlink safe answer is below:

#!/bin/bash
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"  # cd current directory

readlink -f is required to resolve the absolute path of the potentially symlinked file.

The quotes are required to support filepaths that could potentially contain whitespace (bad practice, but its not safe to assume this won't be the case)

James McGuigan
  • 7,542
  • 4
  • 26
  • 29
12

There are a lot of correct answers in here, but one that tends to be more useful for me (making sure a script's relative paths remain predictable/work) is to use pushd/popd:

pushd "$(dirname ${BASH_SOURCE:0})"
trap popd EXIT

# ./xyz, etc...

This will push the source file's directory on to a navigation stack, thereby changing the working directory, but then, when the script exits (for whatever reason, including failure), the trap will run popd, restoring the current working directory before it was executed. If the script were to cd and then fail, your terminal could be left in an unpredictable state after the execution ends - the trap prevents this.

Andrew Theken
  • 3,392
  • 1
  • 31
  • 54
9

This script seems to work for me:

#!/bin/bash
mypath=`realpath $0`
cd `dirname $mypath`
pwd

The pwd command line echoes the location of the script as the current working directory no matter where I run it from.

Amardeep AC9MF
  • 18,464
  • 5
  • 40
  • 50
3

I take this and it works.

#!/bin/bash
cd "$(dirname "$0")"
CUR_DIR=$(pwd)
Robert Hu
  • 131
  • 1
  • 5
2

Get the real path to your script

if [ -L $0 ] ; then
    ME=$(readlink $0)
else
    ME=$0
fi
DIR=$(dirname $ME)

(This is answer to the same my question here: Get the name of the directory where a script is executed)

Community
  • 1
  • 1
rodnower
  • 1,365
  • 3
  • 22
  • 30
1
cd "`dirname $(readlink -f ${0})`"
  • 1
    Can you explain your answer please ? – Zulu Oct 27 '15 at 12:31
  • 2
    Although this code may help to solve the problem, it doesn't explain _why_ and/or _how_ it answers the question. Providing this additional context would significantly improve its long-term educational value. Please [edit] your answer to add explanation, including what limitations and assumptions apply. – Toby Speight Oct 24 '16 at 16:16
  • ${0} would give the file name of the script. readlink -f {0} would give that script's absolute path. dirname would extract the script located path from previous result. – purushothaman poovai Jul 21 '22 at 06:17
1

Most answers either don't handle files which are symlinked via a relative path, aren't one-liners or don't handle BSD (Mac). A solution which does all three is:

HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo .)")"; pwd)

First, cd to bash's conception of the script's directory. Then readlink the file to see if it is a symlink (relative or otherwise), and if so, cd to that directory. If not, cd to the current directory (necessary to keep things a one-liner). Then echo the current directory via pwd.

You could add -- to the arguments of cd and readlink to avoid issues of directories named like options, but I don't bother for most purposes.

You can see the full explanation with illustrations here:

https://www.binaryphile.com/bash/2020/01/12/determining-the-location-of-your-script-in-bash.html

Binary Phile
  • 2,538
  • 16
  • 16
-8
echo $PWD

PWD is an environment variable.

Julian
  • 33,915
  • 22
  • 119
  • 174
-10

If you just need to print present working directory then you can follow this.

$ vim test

#!/bin/bash
pwd
:wq to save the test file.

Give execute permission:

chmod u+x test

Then execute the script by ./test then you can see the present working directory.

Keith Smiley
  • 61,481
  • 12
  • 97
  • 110
Chandan
  • 1
  • 1
  • 4
    The question was how to ensure the script runs in its _own_ directory, including if that is _not_ the working directory. So this is not an answer. Anyway, why do this instead of just... running `pwd`? Seems like a lot of wasted keypresses to me. – underscore_d Oct 23 '16 at 19:01