Reputation: 36599
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.
Upvotes: 836
Views: 664090
Reputation: 37895
#!/usr/bin/env bash
cd "$(dirname "$0")"
How does this work and how does it deal with edge and corner cases?
bash
with some name which tells bash
the script to run.bash
then calls dirname
with the bash argument which points to the script.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 |
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?
Elaborating on the two cases of arguments starting with hyphens in above table (marked with * and **, respectively):
* 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 named --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 naming 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 to impossible to 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.
The TL;DR snippet also works with non-bash /bin/sh
.
Upvotes: 1010
Reputation: 166879
Try the following simple one-liners:
dir="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
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:
How to detect the current directory in which I run my shell script?
How do I get the directory where a Bash script is located from within the script itself?
Reliable way for a Bash script to get the full path to itself
See also:
How can I get the behavior of GNU's readlink -f on a Mac?
Upvotes: 257
Reputation: 8106
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)
Upvotes: 24
Reputation: 3480
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.
Upvotes: 18
Reputation: 2668
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
Upvotes: 1
Reputation: 171
I take this and it works.
#!/bin/bash
cd "$(dirname "$0")"
CUR_DIR=$(pwd)
Upvotes: 4
Reputation: 4073
The following also works:
cd "${0%/*}"
The syntax is thoroughly described in this StackOverflow answer.
Upvotes: 400
Reputation: 1
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.
Upvotes: -12
Reputation: 1405
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)
Upvotes: 2
Reputation: 19064
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.
Upvotes: 12