Reliable way for a Bash script to get the full path to itself

BashPath

Bash Problem Overview


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 https://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in 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 https://stackoverflow.com/questions/3572030/bash-script-absolute-path-with-osx (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).

Bash Solutions


Solution 1 - Bash

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

Solution 2 - Bash

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`

Solution 3 - Bash

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

Solution 4 - Bash

I just had to revisit this issue today and found https://stackoverflow.com/questions/59895/getting-the-source-directory-of-a-bash-script-from-within/246128#246128:

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.

Solution 5 - Bash

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

Solution 6 - Bash

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.

Solution 7 - Bash

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.

Solution 8 - Bash

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

Solution 9 - Bash

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:

Solution 10 - Bash

Simply:

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

Fancy operators are not needed.

Solution 11 - Bash

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

Solution 12 - Bash

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.

Solution 13 - Bash

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?

Solution 14 - Bash

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)

Solution 15 - Bash

Bourne shell (sh) compliant way:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`

Solution 16 - Bash

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.

Solution 17 - Bash

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

https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-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.

Solution 18 - Bash

One liner

`dirname $(realpath $0)`

Solution 19 - Bash

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

Solution 20 - Bash

Try this:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

Solution 21 - Bash

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

Solution 22 - Bash

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

Solution 23 - Bash

More simply, this is what works for me:

MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionT.J. CrowderView Question on Stackoverflow
Solution 1 - BashT.J. CrowderView Answer on Stackoverflow
Solution 2 - BashDarshan Rivka WhittleView Answer on Stackoverflow
Solution 3 - BashAndrew NorrieView Answer on Stackoverflow
Solution 4 - BashFelix RabeView Answer on Stackoverflow
Solution 5 - BashGreenFoxView Answer on Stackoverflow
Solution 6 - BashMattView Answer on Stackoverflow
Solution 7 - BashypidView Answer on Stackoverflow
Solution 8 - BashgerardwView Answer on Stackoverflow
Solution 9 - BashkenorbView Answer on Stackoverflow
Solution 10 - BashpoussmaView Answer on Stackoverflow
Solution 11 - BashStormcloudView Answer on Stackoverflow
Solution 12 - BashAsymLabsView Answer on Stackoverflow
Solution 13 - BashAsymLabsView Answer on Stackoverflow
Solution 14 - BashNordlöwView Answer on Stackoverflow
Solution 15 - BashHaruo KinoshitaView Answer on Stackoverflow
Solution 16 - BashVonCView Answer on Stackoverflow
Solution 17 - BashwichView Answer on Stackoverflow
Solution 18 - BashAbhijitView Answer on Stackoverflow
Solution 19 - BashandroView Answer on Stackoverflow
Solution 20 - BashdiyismView Answer on Stackoverflow
Solution 21 - BashwichView Answer on Stackoverflow
Solution 22 - BashMeowView Answer on Stackoverflow
Solution 23 - BashElendurwenView Answer on Stackoverflow