How can I set the current working directory to the directory of the script in Bash?
Asked Answered
F

13

801

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.

Furthermore answered 28/7, 2010 at 0:43 Comment(5)
Have you considered putting a wrapper script somewhere like /usr/bin to cd into the (hardcoded) proper directory and then execute your script?Erick
Why do you need the directory of the script? There's probably a better way to solve the underlying problem.Atronna
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?Outbound
possible duplicate of Can a Bash script tell what directory it's stored in?Companionable
cd "${BASH_SOURCE%/*}" || exitAppendicectomy
O
960

TL;DR

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

The long winded explanation

How does this work and how does it deal with edge and corner cases?

You type a command launching the script in your interactive shell. That interactive shell calls bash with some name which tells bash the script to run. That bash then calls dirname with the bash argument which points to the script. Finally, that bash then calls cd with the output of dirname as its argument.

script invocation command bash argument dirname argument cd argument
foo (found in $PATH at /path/to/foo) /path/to/foo /path/to/foo /path/to
bash foo foo foo .
/foo /foo /foo /
./foo ./foo ./foo .
"/pa th/to/foo" /pa th/to/foo /pa th/to/foo /pa th/to
"./pa th/to/foo" ./pa th/to/foo ./pa th/to/foo ./pa th/to
"../pa th/to/foo" ../pa th/to/foo ../pa th/to/foo ../pa th/to
"../../pa th/to/foo" ../../pa th/to/foo ../../pa th/to/foo ../../pa th/to
"pa th/to/foo" pa th/to/foo pa th/to/foo pa th/to
--help/foo --help/foo * N/A N/A
--help N/A ** N/A N/A

On symlinks

The cd command will follow symlinks if they are involved. A symlink usually exists to be followed, so in most situations following the symlink is the correct thing to do. Why would it be a problem for the code in the script to follow a symlink when it was just fine to follow that same symlink a few microseconds ago when loading the script?

On command arguments starting with hyphens

* There is only one case where the argument to the dirname could begin with a -, and that is the relative path case --help/foo. If the script is in a subdirectory called --help, the script execution will run bash --help/foo, and bash does not know that option --help/foo and will therefore abort with an error message. The script will never execute, so it does not matter what the cd "$(dirname "$0")" would have executed.

** Note that calling the script --help makes the shell not find the command when you are typing --help in the same directory. Alternatively, with $PATH containing the current directory, the script will be run as /path/to/--help or ./--help, always with something in front of the - character.

Unless bash introduces command line arguments with a parameter separated by a =, it is unlikely that you could pass a - argument to bash which contains a / later, and which is accepted by bash.

If you can rely on dirname accepting -- argument (bash builtin cd will certainly accept --), you can change the script snippet to

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

Please do comment if you can figure out a way to construct an argument beginning with - which can be sneaked past bash.

Nota bene

The above snippet also works with non-bash /bin/sh.

Oscar answered 28/7, 2010 at 17:4 Comment(19)
dirname returns '.' when using bash under Windows. So, Paul's answer is better.Mchale
Also returns '.' in Mac OSXScorbutic
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.Moll
Returns '.' on Ubuntu 14.Anticlerical
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.Oscar
Perhaps cd "$(dirname -- "$0")" is somewhat better in handling situation where $0 starts with a minus sign.Zellers
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 sourceing the script, or possibly some other tool will execute it in the same process.Surpassing
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]})"Smokestack
@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.Oscar
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.Marras
@dimpiax: Using backticks instead of proper quoting fails for directories whose name contains spaces. I reverted your edit adding a backtick based alternative.Oscar
The quotation marks seem messed up. Shouldn't you escape the inner ones? cd "$(dirname \"$0\")"Handbill
@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.Oscar
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.Achernar
@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.Oscar
@lyuboslavkanev, ...perhaps you were thinking of backtick-based command substitution syntax, where escaping inner quotes is needed; that's not the case for $(...).Subservience
Declare dirname = $(pwd) to route to current directoryMccahill
@SanketPandya That makes no sense. What are you trying to achieve?Oscar
@Achernar If you're doing only one cd, you can return by doing cd - after the script completes.Handbill
E
395

The following also works:

cd "${0%/*}"

The syntax is thoroughly described in this StackOverflow answer.

Erection answered 3/5, 2013 at 0:21 Comment(12)
When using bash on Windows use ${0%[\\/]*}Mchale
Sounds interesting, but it doesn't work from the current directory (name of the script is passed instead).Companionable
Explanation how it works: stackoverflow.com/questions/6393551/…Companionable
Don't forget to enclose in quotes if the path contains whitespace. i.e. cd "${0%/*}"Launalaunce
How do you deal with a directory that has spaces in it??! The above code works if the directory is clean but if it has space it fails.Latoyialatreece
@nus IMHO unreadable is ok for very short strings like this that are always going to be copy-pasted, and don't really need to be understood .Hindoo
This fails if the script is called with bash script.sh -- $0 will just be the name of the fileBabineaux
neither it works if a script is in the root directory, because "${0%/*}" would expand to empty stringCough
In zsh, this starts a new shell.Terris
I would say this answer is too succinct. You hardly learn anything about the syntax. Some description about how to read this would be helpful.Familiarity
This trick does not spawn a subshell too so is good for speed freaks.Jurkoic
This returns /bin when run from WSL 2 on Windows.Caliche
C
256

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?

Companionable answered 19/7, 2013 at 11:8 Comment(12)
a much better answer than the popular ones because it resolves syslinks, and accounts for different OS's. Thanks!Kinross
Thanks for this answer. The top one worked on my Mac... but what does the -- switch do in the cp command, @kenorb?Moron
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.Companionable
I'd remove my upvote if I could: realpath is deprecated unix.stackexchange.com/questions/136494/…Waki
@Waki It says that only Debian realpath package is deprecated, not GNU realpath. If you think it's not clear, you can suggest an edit.Companionable
realpath is not available in Mac OS CatalinaMackay
Shouldn't all the examples like cd "$(dirname "$(realpath "$0")")" also use -- before the parameter, like cd "$(dirname -- "$(realpath -- "$0")")"?Idiolect
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?Multure
@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.Veronicaveronika
@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.Multure
@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.Veronicaveronika
@FWDekker: I typically don't use whitespace in file names, but it's still a good habit to use double quotes. Thanks.Multure
B
98
cd "$(dirname "${BASH_SOURCE[0]}")"

It's easy. It works.

Bondstone answered 27/4, 2012 at 10:28 Comment(5)
This should be the accepted answer. Works on OSX and Linux Ubuntu.Venu
This works great when the script B is sourced from other script A and you need to use paths relative to script B. Thank you.Breakaway
This also works in Cygwin for those of us unfortunate enough to have to touch Windows.Smokestack
Or cd "${BASH_SOURCE%/*}" || exitAppendicectomy
Works on macOS MojaveFreeloader
S
21

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)

Skivvy answered 2/8, 2018 at 10:42 Comment(0)
T
16

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.

Tournament answered 10/6, 2021 at 12:4 Comment(0)
M
11

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.

Maxfield answered 28/7, 2010 at 0:59 Comment(3)
realpath is unlikely to be installed everywhere. Which may not matter, depending on the OP's situation.Atronna
My system doesn't have realpath but it does have readlink which seems to be similar.Lorenelorens
Which dist doesn't have realpath installed by default? Ubuntu has it.Companionable
T
3

I take this and it works.

#!/bin/bash
cd "$(dirname "$0")"
CUR_DIR=$(pwd)
Topdrawer answered 8/9, 2020 at 3:16 Comment(0)
D
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)

Downs answered 30/7, 2010 at 19:8 Comment(0)
S
1
cd "`dirname $(readlink -f ${0})`"
Sanfordsanfourd answered 27/10, 2015 at 6:16 Comment(3)
Can you explain your answer please ?Treva
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.Crystlecs
${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.Trudey
H
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

Hilliard answered 18/4, 2021 at 13:40 Comment(0)
V
-9
echo $PWD

PWD is an environment variable.

Vanettavang answered 5/5, 2017 at 14:36 Comment(1)
This only gives the directory called from, not the directory where the script is.Stearic
N
-11

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.

Nicholasnichole answered 15/7, 2013 at 16:38 Comment(1)
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.Britney

© 2022 - 2024 — McMap. All rights reserved.