921

I have a Bash script that needs to know its full path. I'm trying to find a broadly-compatible way of doing that without ending up with relative or funky-looking paths. I only need to support Bash, not sh, csh, etc.

What I've found so far:

  1. The accepted answer to Getting the source directory of a Bash script from within addresses getting the path of the script via dirname $0, which is fine, but that may return a relative path (like .), which is a problem if you want to change directories in the script and have the path still point to the script's directory. Still, dirname will be part of the puzzle.

  2. The accepted answer to Bash script absolute path with OS X (OS X specific, but the answer works regardless) gives a function that will test to see if $0 looks relative and if so will pre-pend $PWD to it. But the result can still have relative bits in it (although overall it's absolute) — for instance, if the script is t in the directory /usr/bin and you're in /usr and you type bin/../bin/t to run it (yes, that's convoluted), you end up with /usr/bin/../bin as the script's directory path. Which works, but...

  3. The readlink solution on this page, which looks like this:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    

    But readlink isn't POSIX and apparently the solution relies on GNU's readlink where BSD's won't work for some reason (I don't have access to a BSD-like system to check).

So, various ways of doing it, but they all have their caveats.

What would be a better way? Where "better" means:

  • Gives me the absolute path.
  • Takes out funky bits even when invoked in a convoluted way (see comment on #2 above). (E.g., at least moderately canonicalizes the path.)
  • Relies only on Bash-isms or things that are almost certain to be on most popular flavors of *nix systems (GNU/Linux, BSD and BSD-like systems like OS X, etc.).
  • Avoids calling external programs if possible (e.g., prefers Bash built-ins).
  • (Updated, thanks for the heads up, wich) It doesn't have to resolve symlinks (in fact, I'd kind of prefer it left them alone, but that's not a requirement).
ribbons
  • 31
  • 6
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 32
    Please see [BashFAQ/028](http://mywiki.wooledge.org/BashFAQ/028). – Dennis Williamson Jan 23 '11 at 15:11
  • 1
    The link in solution #3 above is dead. Anyone have an updated one? – the Tin Man Jun 04 '14 at 18:12
  • 4
    $(readlink -f $0) - doesn't work on Mac OS 10.9.2 – Bogdan Nechyporenko Oct 24 '14 at 19:39
  • use GNU readlink via homebrew to replace the BSD one – dragonxlwang Apr 01 '16 at 16:49
  • 1
    (1.) the [link you give in your own question](http://stackoverflow.com/q/59895/52074) has about 10x question-upvotes, 10x favorites, >15x answer-upvotes. (2.) Your summary is somewhat disingenious. (The link you gave has a first revision answer of "[DIRECTORY=$(cd `dirname $0` && pwd)](http://stackoverflow.com/revisions/246128/1)" ... which does not match your summary "getting the path of the script via dirname $0"and does not as you say "return a relative path".) – Trevor Boyd Smith Jul 21 '16 at 14:24
  • 1
    This is not exactly a duplicate of http://stackoverflow.com/questions/59895/getting-the-source-directory-of-a-bash-script-from-within . That question is asking for source directory, which could also include relative path (as evidenced by some of the answers suggesting `$0`). This question is specifically absolute path, which is different. – wisbucky Mar 22 '17 at 03:51
  • A robust and Mac/Linux-friendly solution is `HERE=$(cd "$(dirname "$BASH_SOURCE")"; cd -P "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"; pwd)` - explanation (with simpler variations) here: http://www.binaryphile.com/bash/2020/01/12/determining-the-location-of-your-script-in-bash.html – Binary Phile Jun 16 '20 at 18:25

23 Answers23

795

Here's what I've come up with (edit: plus some tweaks provided by sfstewman, levigroker, Kyle Strand, and Rob Kennedy), that seems to mostly fit my "better" criteria:

SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"

That SCRIPTPATH line seems particularly roundabout, but we need it rather than SCRIPTPATH=`pwd` in order to properly handle spaces and symlinks.

The inclusion of output redirection (>/dev/null 2>&1) handles the rare(?) case where cd might produce output that would interfere with the surrounding $( ... ) capture. (Such as cd being overridden to also ls a directory after switching to it.)

Note also that esoteric situations, such as executing a script that isn't coming from a file in an accessible file system at all (which is perfectly possible), is not catered to there (or in any of the other answers I've seen).

The -- after cd and before "$0" are in case the directory starts with a -.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    still no way to tell what the name of the file itself is though ? – quickshiftin Apr 20 '12 at 04:49
  • 15
    This does not work if the script is in a directory from the `$PATH` and you call it `bash scriptname`. In such a case `$0` does not contain any path, just `scriptname`. – pabouk - Ukraine stay strong Jun 15 '15 at 10:40
  • I agree with @pabouk, I am launching my shell script from Python and this `SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"` is returning me nothing – Alexis.Rolland Apr 14 '18 at 02:47
  • 8
    -1 (please don't take it personnaly, it is just so that the real answer cat maybe get closer to the top): I used to do a similar thing (I used: `"$(cd -P "$(dirname "$0")" && pwd)"` until today, but Andrew Norrie's answer covers more cases (ie : PATH="/some/path:$PATH" ; bash "script_in_path" : will only work with his answer, not with yours (as $0 contains only "script_in_path" and no indication of where (in $PATH) bash found it). correct is : `ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"` (ie, @andrew-norrie 's answer: covers all cases, imo) – Olivier Dulac Apr 18 '18 at 12:10
  • clean, nice, simple, and works on WSL! – Felipe Alvarez Jul 13 '18 at 02:51
  • 9
    I would prefer if you used `dirname "${BASH_SOURCE[0]}"` rather than `dirname "$0"` for added support for sourced scripts. – gregtzar Jul 25 '18 at 10:18
  • A minor change to make it just a bit more reliable SCRIPTPATH="$( cd "$(dirname "$0")" && pwd -P )" . The pwd will be executed only if the cd will be succeed. – thanos.a Jun 07 '19 at 06:28
  • @bobpaul - Seeing your comment, I was all set to edit out the extra quotes, but it doesn't work. If I have a script at `/home/username/dir with spaces/temp`, the version in the answer works, but the version from your comment (copied and pasted to avoid introducing errors :-) ) doesn't: `/home/username/temp/dir with spaces/temp: line 4: cd: /home/username/temp/dir: No such file or directory` Which makes sense to me (with my v. meager bash skills), since `cd` receives three arguments, not one. The outermost `"` seem unnecessary, though, this works: `SCRIPTPATH=$( cd "$(dirname "$0")" ; pwd -P )` – T.J. Crowder Jul 10 '19 at 16:55
  • also see this answer (https://stackoverflow.com/a/11114547/6375601) inside this thread. It is using the `realpath` command, which solves some issues. man page: http://man7.org/linux/man-pages/man1/realpath.1.html – nuiun Jan 03 '20 at 09:08
  • `builtin cd` is a simpler way to bypass `cd` aliases than redirecting their output. – Beni Cherniavsky-Paskin May 24 '20 at 12:09
  • 2
    Final tweak : `cd -- "$(dirname "$0")"` to avoid problems with directories that start with a hyphen. – kvantour Mar 03 '21 at 15:25
  • Another more standard case where `cd` will produce output is when its `$CDPATH` variable is set. – j1elo May 06 '21 at 12:10
  • 1
    Thank you, thank you, @gregtczap from 3 years ago. That is exactly the esoteric thing I needed. When you source a script within another script and need the path to the sourced script and not the running script that sourced it, ${BASH_SOURCE[0]} is the droid I'm looking for! – John T. Jun 09 '21 at 18:45
  • So the script itself will be something like this: `$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)/$(echo "$0" | cut -d '/' -f 2)` – aderchox Nov 11 '21 at 22:02
  • Doesn't work when the file is sourced: `. bin/script.sh` will have "/bin" – Dev Null Mar 24 '22 at 20:44
  • Finally!!! I've seen this answered a million times on SE, but this is the first correct answer. – Riveascore Apr 23 '23 at 01:38
374

I'm surprised that the realpath command hasn't been mentioned here. My understanding is that it is widely portable / ported.

Your initial solution becomes:

SCRIPT=$(realpath "$0")
SCRIPTPATH=$(dirname "$SCRIPT")

And to leave symbolic links unresolved per your preference:

SCRIPT=$(realpath -s "$0")
SCRIPTPATH=$(dirname "$SCRIPT")
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Darshan Rivka Whittle
  • 32,989
  • 7
  • 91
  • 109
  • 3
    @bolinfest: realpath is part of GNU coreutils since [Jan 2012](http://git.savannah.gnu.org/cgit/coreutils.git/commit/?id=77ea441f79aa115f79b47d9c1fc9c0004c5c7111) / v8.15. If you're running a recent Linux distro that includes coreutils (which should be basically all of them) and `realpath` is missing, the packagers have certainly gone out of their way to separate realpath into another package. (current coreutils version is 8.21, released 2013-02-04. The version I am running (on Arch Linux) appears to be this one, 8.21, and the package includes `realpath`, as any right-thinking package would ;) – kampu Jun 15 '13 at 13:04
  • 30
    Using realpath, and even shorter: `SCRIPT_PATH=$(dirname $(realpath -s $0))` – GuySoft Jul 08 '13 at 21:28
  • @idonnie Yes there is, you need to download [Xcode](https://developer.apple.com/library/mac/documentation/darwin/reference/manpages/man3/realpath.3.html) and install the command line tools. Or you can use [this implementation](https://github.com/harto/realpath-osx) if you don't want to download gigabytes for such a small thing. – GuySoft Jul 08 '13 at 21:32
  • 1
    It is also an extra package on Debian Wheezy. The coreutils version is 8.13 and does not include it yet. – mrossi Dec 06 '13 at 19:35
  • 1
    I'm on Centos 6, have GNU coreutils 8.4, but no realpath. – toxalot Mar 16 '14 at 07:55
  • 1
    RHEL (Red Hat) 6.3. No realpath. Coreutils version is 8.4-19.el6 – Ogre Psalm33 Aug 13 '14 at 12:46
  • 9
    `realpath` is [not mentioned](http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html) in the POSIX standard. – Totor Feb 24 '15 at 19:10
  • For Mac "home-brew-masters" with "intact-gnu-prefixes", try... `grealpath` – Alex Gray Mar 20 '15 at 13:03
  • 23
    Many if not most of these answers are buggy when given filenames with whitespace. `SCRIPT_PATH=$(dirname "$(realpath -s "$0")")` is a fixed version of GuySoft's answer, though using `$BASH_SOURCE` would be more reliable still than `$0` (and this depends on having `realpath`, a non-POSIX tool, installed). – Charles Duffy Jun 12 '15 at 23:09
  • seems to give only current dir, instead of script dir when `source /path/to/script` – Mzq Apr 09 '18 at 00:22
  • The question did not specify `linux` or `gnu` so this answer adds a dependency. – William Entriken Aug 18 '19 at 18:49
  • On Windows Git Bash, I ended up using: `SCRIPT_PATH=$(dirname "$(realpath -s "$BASH_SOURCE[0]")")`. – Iain Aug 31 '19 at 18:46
  • I think to fully handle whitespace, need to also quote $() at the outer level: `SCRIPT_PATH="$(dirname "$(realpath -s "$0")")"` – Beni Cherniavsky-Paskin May 24 '20 at 12:15
  • On macOS, you can install GNU realpath with `brew install coreutils`. – Benjamin Carlsson Jan 20 '22 at 18:04
222

The simplest way that I have found to get a full canonical path in Bash is to use cd and pwd:

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

Using ${BASH_SOURCE[0]} instead of $0 produces the same behavior regardless of whether the script is invoked as <name> or source <name>.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andrew Norrie
  • 2,229
  • 1
  • 13
  • 4
  • 4
    This doesn't seem to work if the originally-specified file is a symlink. I think you need to use something like readlink (or ls) in a loop, to make sure you've found a final non-symlink file. I've been on the lookout for something more concise, but in any case you can find the last version of the solution I used in the Android codebase, under `dalvik/dx/etc/dx`. – danfuzz Mar 20 '12 at 21:02
  • @danfuzz see Dennis Williamson comment, regarding using -P for pwd. It should do what you want. – over_optimistic Apr 17 '12 at 18:57
  • 6
    @over_optimistic I'm not convinced that -P helps here: If $0 names a symlink, then `cd $(dirname $0); pwd -P` still just tells you what directory the symlink is in and not the physical directory where the actual script resides. You really need to use something like `readlink` on the script name, except that `readlink` is not actually POSIX and it does seem to vary in practice between OSes. – danfuzz Apr 20 '12 at 21:52
  • @danfuzz: http://linux.die.net/man/8/symlinks looks like a good thing to use, to both have "cleaner" symlinks, and find their full path equivalent – Olivier Dulac Apr 23 '14 at 09:33
  • @OlivierDulac `symlinks` seems to be Linux-specific. I don't see it on OS X, and it does not appear to be part of POSIX. – danfuzz Apr 28 '14 at 23:01
  • 7
    +1 works nicely in all reasonable scenarios on a mac. No external dependencies and executes in 1 line. I use it to get the script's directory like so: SCRIPTPATH=$(cd \`dirname "${BASH_SOURCE[0]}"\` && pwd) – Richard Hodges May 25 '14 at 16:02
  • The `$SCRIPTPATH` solution here is the only cross-platform solution I have reliably used on CygWin, MSYS, Linux/GNU and macOS/BSD. – oligofren Apr 26 '18 at 18:23
  • Does not work if the bash script starts with a hyphen. It is more robust to use `dirname -- "${BASH_SOURCE[0]}"`. Same for `basename` and `cd`. – mxmlnkn May 07 '20 at 12:47
63

I just had to revisit this issue today and found Get the source directory of a Bash script from within the script itself:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

There's more variants at the linked answer, e.g. for the case where the script itself is a symlink.

Felix Rabe
  • 4,206
  • 4
  • 25
  • 34
  • 2
    I just found out that the above does not work when the script is straight out executed rather than sourced: https://stackoverflow.com/questions/27880087/weird-behaviour-of-bash-source – Andrei LED Aug 27 '19 at 11:49
42

Get the absolute path of a shell script

It does not use the -f option in readlink, and it should therefore work on BSD/Mac OS X.

Supports

  • source ./script (When called by the . dot operator)
  • Absolute path /path/to/script
  • Relative path like ./script
  • /path/dir1/../dir2/dir3/../script
  • When called from symlink
  • When symlink is nested eg) foo->dir1/dir2/bar bar->./../doe doe->script
  • When caller changes the scripts name

I am looking for corner cases where this code does not work. Please let me know.

Code

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
    cd "`dirname "${SCRIPT_PATH}"`"
    SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd  > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd   =[`pwd`]"

Known issus

The script must be on disk somewhere. Let it be over a network. If you try to run this script from a PIPE it will not work

wget -o /dev/null -O - http://host.domain/dir/script.sh |bash

Technically speaking, it is undefined. Practically speaking, there is no sane way to detect this. (A co-process can not access the environment of the parent.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
GreenFox
  • 1,112
  • 10
  • 5
  • 2
    As stated elsewhere, and it's not quite an edge case I think, but readlink -f is not a standard parameter and very well not be available, e.g. on my BSD. – conny Nov 21 '12 at 13:25
  • I ran into a corner case where this didn't work. I had a script under `~/scripts/myscript` and a symlink at `~/bin/myscript` which pointed to `../scripts/myscript`. Running `~/bin/myscript` from `~/` caused it to think the script's location was `~/`. The solution [here](http://stackoverflow.com/a/246128/817950) worked fine, which looks pretty similar to your solution – redbmk Feb 13 '15 at 19:30
41

Use:

SCRIPT_PATH=$(dirname `which $0`)

which prints to standard output the full path of the executable that would have been executed when the passed argument had been entered at the shell prompt (which is what $0 contains)

dirname strips the non-directory suffix from a file name.

Hence you end up with the full path of the script, no matter if the path was specified or not.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Matt
  • 731
  • 6
  • 7
  • 8
    `dirname ./myscript` returns `.`. This might not be what you want. – Bobby Norton May 22 '14 at 00:09
  • @BobbyNorton Yes because the non-directory suffix at that point is simply `.`. However, if you run `which` on the script name and store it in a variable, such as `a=$(which ./myscript)`, it will return the full path, such as `/tmp/myscript`, which if passed to `dirname` will return the path. Interestingly if you run `which ./myscript` and not assign it to a variable, it simply returns `./myscript`. I suspect this is because when assigning it to a variable it executes in another shell and passes the complete path to bash at that time. – Matt May 24 '14 at 00:32
  • 3
    Unfortunately, this doesn't appear to work on OS X. It would be a nice solution if it did! – Aron Ahmadia Mar 24 '15 at 20:34
  • 2
    @Matt I would not use the 'which' command unless the command is in the current path. Which only searches for the command in the path. If your script is not in the path it will never be found. – muman Mar 15 '16 at 22:39
  • If this script is sourced, $0 will refer to the 'sourcing' script, and may not give you what you want – Geof Sawaya Feb 17 '19 at 05:03
29

As realpath is not installed per default on my Linux system, the following works for me:

SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"

$SCRIPT will contain the real file path to the script and $SCRIPTPATH the real path of the directory containing the script.

Before using this read the comments of this answer.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ypid
  • 1,708
  • 14
  • 10
  • 3
    The OP already noted this solution and disregarded it for not being POSIX -- but this is nice and tidy for GNU-based systems at least. The key feature of this is that **it resolves symbolic links**. – Brent Bradburn May 17 '13 at 17:42
19

Easy to read? Below is an alternative. It ignores symlinks

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

echo -n "current "
pwd
echo script $currentDir

Since I posted the above answer a couple years ago, I've evolved my practice to using this linux specific paradigm, which properly handles symlinks:

ORIGIN=$(dirname $(readlink -f $0))
gerardw
  • 5,822
  • 46
  • 39
  • I get ``bash: currentDir: command not found`` – muon Sep 19 '16 at 15:40
  • 1
    This will only resolve the symlink of directories. It will fail to resolve the symlink for a path. Consider when $0 is a link to a script in the same directory. – gesell Oct 21 '16 at 12:49
17

Simply:

BASEDIR=$(readlink -f $0 | xargs dirname)

Fancy operators are not needed.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
poussma
  • 7,033
  • 3
  • 43
  • 68
15

You may try to define the following variable:

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

Or you can try the following function in Bash:

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

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

Related:

kenorb
  • 155,785
  • 88
  • 678
  • 743
13

Answering this question very late, but I use:

SCRIPT=$( readlink -m $( type -p ${0} ))      # Full path to script handling Symlinks
BASE_DIR=`dirname "${SCRIPT}"`                # Directory script is run in
NAME=`basename "${SCRIPT}"`                   # Actual name of script even if linked
Stormcloud
  • 2,065
  • 2
  • 21
  • 41
  • The full path provided is exactly what I was looking for. Compact, efficient, reliable. Well done! – lucaferrario Oct 04 '16 at 12:43
  • Not all implementation of readlink have `-m` I believe the OP is looking for a solution that does not depend on GNU readlink extended functionality. – RubenLaguna Aug 10 '17 at 09:37
  • 1
    I've just tried a lot of these solutions, and this and @GreenFox 's answer are the only ones that work, when I have a symlink to the script in PATH, and call it like "bash -e my_script" – miyalys Apr 12 '20 at 01:21
  • 1
    If you have to go for `sudo -s source somescript.sh` (i.e. fat32 partion without chance to set +x bit) this does not work ( while `${BASH_SOURCE[0]}` still does) – Frank N May 04 '21 at 17:59
8

We have placed our own product realpath-lib on GitHub for free and unencumbered community use.

Shameless plug but with this Bash library you can:

get_realpath <absolute|relative|symlink|local file>

This function is the core of the library:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

It doesn't require any external dependencies, just Bash 4+. Also contains functions to get_dirname, get_filename, get_stemname and validate_path validate_realpath. It's free, clean, simple and well documented, so it can be used for learning purposes too, and no doubt can be improved. Try it across platforms.

Update: After some review and testing we have replaced the above function with something that achieves the same result (without using dirname, only pure Bash) but with better efficiency:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

This also includes an environment setting no_symlinks that provides the ability to resolve symlinks to the physical system. By default it keeps symlinks intact.

AsymLabs
  • 933
  • 9
  • 15
  • Thank you Ben. New to the site (as a contributor) and I have amended the entry as required. I didn't think that there were any commercial issues here since we are giving the code away without restriction. We also see it as a good learning tool and have documented it thoroughly for that purpose. – AsymLabs Oct 02 '13 at 19:52
  • 1
    Unfortunately, the symlink resolution in the updated `get_realpath` doesn't work for the last (basename) part of the input path, only the earlier (dirname) parts. I opened an issue for this in the Github repo. Hopefully there is a solution for this so that we can get a pure bash equivalent of `readlink -f`. https://github.com/AsymLabs/realpath-lib/issues/1 – Mikael Auno Nov 06 '13 at 12:01
  • @Mikael Auno You have supplied an excellent test case. We've picked up the discussion at github and will have look at it. – AsymLabs Nov 07 '13 at 18:14
  • Note that the second version is also 100% POSIX compatible and works unmodified in at least `dash`! :+1: – ntninja Mar 09 '20 at 10:45
  • You might put some install instructions in the Readme. I tried installing this but could not get it to work. – armand Jan 20 '21 at 17:54
  • @ntninja The second version is definitely not POSIX as it uses `function`, `[[`, and `local` – Andrey Kaipov Mar 18 '21 at 05:57
  • Is the current version taking Mikael's comments into account? If not, perhaps update the answer. – Anne van Rossum Sep 14 '22 at 11:57
4

Considering this issue again: there is a very popular solution that is referenced within this thread that has its origin here:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

I have stayed away from this solution because of the use of dirname - it can present cross-platform difficulties, particularly if a script needs to be locked down for security reasons. But as a pure Bash alternative, how about using:

DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"

Would this be an option?

Community
  • 1
  • 1
AsymLabs
  • 933
  • 9
  • 15
  • BASH_SOURCE seems to be necessary for running the script using `PATH=/path/to/your/script:$PATH yourscript.sh`. Unfortunately, this needs bash – Daniel Alder Jan 27 '15 at 09:37
4

If we use Bash I believe this is the most convenient way as it doesn't require calls to any external commands:

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
Nordlöw
  • 11,838
  • 10
  • 52
  • 99
4

One liner

`dirname $(realpath $0)`
Abhijit
  • 93
  • 2
4

Bourne shell (sh) compliant way:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
Mohnish
  • 1,010
  • 1
  • 12
  • 20
  • 1
    Why do we need `while read a` part? Why don't just `cd $(dirname $a) && pwd`? – Gill Bates Dec 25 '16 at 12:04
  • 2
    Because `$( ... )` won't work in no-bash shell. I saw bad script that is using `$( ... )` expression and begins `#!/bin/sh` manytimes. I recommend to write `#!/bin/bash` at beginning or stop using `$( ... )` expression. This example for 1st recommendation. – Haruo Kinoshita Dec 26 '16 at 15:53
  • Sorry for mistake. "This example is for 2nd recommendation" – Haruo Kinoshita Dec 26 '16 at 15:55
  • 1
    @HaruoKinoshita That’s incorrect, [`$(…)` is valid syntax for command substitution in POSIX sh](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_03). At any rate this doesn’t explain why your answer uses an unnecessary pipe and `while` loop. – Konrad Rudolph Jun 24 '22 at 11:24
3

The accepted solution has the inconvenient (for me) to not be "source-able":
if you call it from a "source ../../yourScript", $0 would be "bash"!

The following function (for bash >= 3.0) gives me the right path, however the script might be called (directly or through source, with an absolute or a relative path):
(by "right path", I mean the full absolute path of the script being called, even when called from another path, directly or with "source")

#!/bin/bash
echo $0 executed

function bashscriptpath() {
  local _sp=$1
  local ascript="$0"
  local asp="$(dirname $0)"
  #echo "b1 asp '$asp', b1 ascript '$ascript'"
  if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}"
  elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd)
  else
    if [[ "$ascript" == "bash" ]] ; then
      ascript=${BASH_SOURCE[0]}
      asp="$(dirname $ascript)"
    fi  
    #echo "b2 asp '$asp', b2 ascript '$ascript'"
    if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;
    elif [[ "${ascript#../}" != "$ascript" ]]; then
      asp=$(pwd)
      while [[ "${ascript#../}" != "$ascript" ]]; do
        asp=${asp%/*}
        ascript=${ascript#../}
      done
    elif [[ "${ascript#*/}" != "$ascript" ]];  then
      if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi
    fi  
  fi  
  eval $_sp="'$asp'"
}

bashscriptpath H
export H=${H}

The key is to detect the "source" case and to use ${BASH_SOURCE[0]} to get back the actual script.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
2

Perhaps the accepted answer to the following question may be of help.

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

Given that you just want to canonicalize the name you get from concatenating $PWD and $0 (assuming that $0 is not absolute to begin with), just use a series of regex replacements along the line of abs_dir=${abs_dir//\/.\//\/} and such.

Yes, I know it looks horrible, but it'll work and is pure Bash.

pevik
  • 4,523
  • 3
  • 33
  • 44
wich
  • 16,709
  • 6
  • 47
  • 72
  • Thanks. Re the linked question, it still relies on changing the directory and using `pwd`, which is what I find clunky about my solution. The regex is interesting. I can't help but worry about edge cases, though. – T.J. Crowder Jan 23 '11 at 13:41
  • Yes, it would need some good testing, but as far as I'm aware there is no portable one-shot solution that will canonicalize a name for you. – wich Jan 23 '11 at 13:45
  • Oh and of course the regexing will only work on *nix, it'll bomb on a cygwin environment or such. – wich Jan 23 '11 at 13:48
1

I have used the following approach successfully for a while (not on OS X though), and it only uses a shell built-in and handles the 'source foobar.sh' case as far as I have seen.

One issue with the (hastily put together) example code below is that the function uses $PWD which may or may not be correct at the time of the function call. So that needs to be handled.

#!/bin/bash

function canonical_path() {
  # Handle relative vs absolute path
  [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
  # Change to dirname of x
  cd ${x%/*}
  # Combine new pwd with basename of x
  echo $(pwd -P)/${x##*/}
  cd $OLDPWD
}

echo $(canonical_path "${BASH_SOURCE[0]}")

type [
type cd
type echo
type pwd
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
andro
  • 11
  • 2
1

Try this:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
AstroCB
  • 12,337
  • 20
  • 57
  • 73
diyism
  • 12,477
  • 5
  • 46
  • 46
0

Just for the hell of it I've done a bit of hacking on a script that does things purely textually, purely in Bash. I hope I caught all the edge cases.

Note that the ${var//pat/repl} that I mentioned in the other answer doesn't work since you can't make it replace only the shortest possible match, which is a problem for replacing /foo/../ as e.g. /*/../ will take everything before it, not just a single entry. And since these patterns aren't really regexes I don't see how that can be made to work. So here's the nicely convoluted solution I came up with, enjoy. ;)

By the way, let me know if you find any unhandled edge cases.

#!/bin/bash

canonicalize_path() {
  local path="$1"
  OIFS="$IFS"
  IFS=$'/'
  read -a parts < <(echo "$path")
  IFS="$OIFS"

  local i=${#parts[@]}
  local j=0
  local back=0
  local -a rev_canon
  while (($i > 0)); do
    ((i--))
    case "${parts[$i]}" in
      ""|.) ;;
      ..) ((back++));;
      *) if (($back > 0)); then
           ((back--))
         else
           rev_canon[j]="${parts[$i]}"
           ((j++))
         fi;;
    esac
  done
  while (($j > 0)); do
    ((j--))
    echo -n "/${rev_canon[$j]}"
  done
  echo
}

canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
wich
  • 16,709
  • 6
  • 47
  • 72
-1

Yet another way to do this:

shopt -s extglob

selfpath=$0
selfdir=${selfpath%%+([!/])}

while [[ -L "$selfpath" ]];do
  selfpath=$(readlink "$selfpath")
  if [[ ! "$selfpath" =~ ^/ ]];then
    selfpath=${selfdir}${selfpath}
  fi
  selfdir=${selfpath%%+([!/])}
done

echo $selfpath $selfdir
Meow
  • 4,341
  • 1
  • 18
  • 17
-4

More simply, this is what works for me:

MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh
Lenka Pitonakova
  • 979
  • 12
  • 14