Question

How do I get the directory where a Bash script is located from within the script itself?

How do I get the path of the directory in which a Bash script is located, inside that script?

I want to use a Bash script as a launcher for another application. I want to change the working directory to the one where the Bash script is located, so I can operate on the files in that directory, like so:

$ ./application
 6284  2638676  6284
1 Jan 1970

Solution

 8133
#!/usr/bin/env bash

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

is a useful one-liner which will give you the full directory name of the script no matter where it is being called from.

It will work as long as the last component of the path used to find the script is not a symlink (directory links are OK). If you also want to resolve any links to the script itself, you need a multi-line solution:

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
  SOURCE=$(readlink "$SOURCE")
  [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )

This last one will work with any combination of aliases, source, bash -c, symlinks, etc.

Beware: if you cd to a different directory before running this snippet, the result may be incorrect!

Also, watch out for $CDPATH gotchas, and stderr output side effects if the user has smartly overridden cd to redirect output to stderr instead (including escape sequences, such as when calling update_terminal_cwd >&2 on Mac). Adding >/dev/null 2>&1 at the end of your cd command will take care of both possibilities.

To understand how it works, try running this more verbose form:

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -L "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET=$(readlink "$SOURCE")
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE=$TARGET
  else
    DIR=$( dirname "$SOURCE" )
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE=$DIR/$TARGET # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR=$( dirname "$SOURCE" )
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

And it will print something like:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
2008-10-29
dogbane

Solution

 1266

Use dirname "$0":

test.sh:

#!/usr/bin/env bash

echo "The script you are running has:"
echo "basename: [$(basename "$0")]"
echo "dirname : [$(dirname "$0")]"
echo "pwd     : [$(pwd)]"

Using pwd alone will not work if you are not running the script from the directory it is contained in.

[~]$ pwd
/home/matt
[~]$ ./test.sh
The script you are running has:
basename: [test.sh]
dirname : [/home/matt]
pwd     : [/home/matt]

[~]$ cd /tmp
[~/tmp]$ ~/test.sh
The script you are running has:
basename: [test.sh]
dirname : [/home/matt]
pwd     : [/tmp]
2008-09-12
matt b

Solution

 689

The dirname command is the most basic, simply parsing the path up to the filename off of the $0 (script name) variable:

dirname -- "$0";

But, as matt b pointed out, the path returned is different depending on how the script is called. pwd doesn't do the job because that only tells you what the current directory is, not what directory the script resides in. Additionally, if a symbolic link to a script is executed, you're going to get a (probably relative) path to where the link resides, not the actual script.

Some others have mentioned the readlink command, but at its simplest, you can use:

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

readlink will resolve the script path to an absolute path from the root of the filesystem. So, any paths containing single or double dots, tildes and/or symbolic links will be resolved to a full path.

Here's a script demonstrating each of these, whatdir.sh:

#!/usr/bin/env bash

echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename -- "$0"`"
echo "dirname: `dirname -- "$0"`"
echo "dirname/readlink: $( dirname -- "$( readlink -f -- "$0"; )"; )"

Running this script in my home dir, using a relative path:

>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

Again, but using the full path to the script:

>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Now changing directories:

>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

And finally using a symbolic link to execute the script:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

There is however one case where this doesn't work, when the script is sourced (instead of executed) in bash:

>>>$ cd /tmp
>>>$ . ~/whatdir.sh  
pwd: /tmp
$0: bash
basename: bash
dirname: .
dirname/readlink: /tmp
2009-09-26
phatblat

Solution

 205
pushd . > '/dev/null';
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";

while [ -h "$SCRIPT_PATH" ];
do
    cd "$( dirname -- "$SCRIPT_PATH"; )";
    SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";
done

cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null';
SCRIPT_PATH="$( pwd; )";
popd  > '/dev/null';

It works for all versions, including

  • when called via multiple depth soft link,
  • when the file it
  • when script called by command "source" aka . (dot) operator.
  • when arg $0 is modified from caller.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternatively, if the Bash script itself is a relative symlink you want to follow it and return the full path of the linked-to script:

pushd . > '/dev/null';
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}";

while [ -h "$SCRIPT_PATH" ];
do
    cd "$( dirname -- "$SCRIPT_PATH"; )";
    SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )";
done

cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null';
SCRIPT_PATH="$( pwd; )";
popd  > '/dev/null';

SCRIPT_PATH is given in full path, no matter how it is called.

Just make sure you locate this at start of the script.

2008-10-07
user25866

Solution

 149

Here is an easy-to-remember script:

DIR="$( dirname -- "${BASH_SOURCE[0]}"; )";   # Get the directory name
DIR="$( realpath -e -- "$DIR"; )";    # Resolve its full path if need be
2018-11-07
Thamme Gowda

Solution

 145

You can use $BASH_SOURCE:

#!/usr/bin/env bash

scriptdir="$( dirname -- "$BASH_SOURCE"; )";

Note that you need to use #!/bin/bash and not #!/bin/sh since it's a Bash extension.

2008-09-12
Mr Shark

Solution

 127

Short answer:

"`dirname -- "$0";`"

or (preferably):

"$( dirname -- "$0"; )"
2008-12-03
Fabien

Solution

 122

This should do it:

DIR="$(dirname "$(realpath "$0")")"

This works with symlinks and spaces in path.

Please see the man pages for dirname and realpath.

Please add a comment on how to support MacOS. I'm sorry I can verify it.

2016-02-12
Simon Rigét

Solution

 76

pwd can be used to find the current working directory, and dirname to find the directory of a particular file (command that was run, is $0, so dirname $0 should give you the directory of the current script).

However, dirname gives precisely the directory portion of the filename, which more likely than not is going to be relative to the current working directory. If your script needs to change directory for some reason, then the output from dirname becomes meaningless.

I suggest the following:

#!/usr/bin/env bash

reldir="$( dirname -- "$0"; )";
cd "$reldir";
directory="$( pwd; )";

echo "Directory is ${directory}";

This way, you get an absolute, rather than a relative directory.

Since the script will be run in a separate Bash instance, there isn't any need to restore the working directory afterwards, but if you do want to change back in your script for some reason, you can easily assign the value of pwd to a variable before you change directory, for future use.

Although just

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

solves the specific scenario in the question, I find having the absolute path to more more useful generally.

2008-09-15
SpoonMeiser

Solution

 43

This gets the current working directory on Mac OS X v10.6.6 (Snow Leopard):

DIR=$(cd "$(dirname "$0")"; pwd)
2010-12-30
Pubguy

Solution

 42
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
2010-10-07
P M

Solution

 41

I don't think this is as easy as others have made it out to be. pwd doesn't work, as the current directory is not necessarily the directory with the script. $0 doesn't always have the information either. Consider the following three ways to invoke a script:

./script

/usr/bin/script

script

In the first and third ways $0 doesn't have the full path information. In the second and third, pwd does not work. The only way to get the directory in the third way would be to run through the path and find the file with the correct match. Basically the code would have to redo what the OS does.

One way to do what you are asking would be to just hardcode the data in the /usr/share directory, and reference it by its full path. Data shoudn't be in the /usr/bin directory anyway, so this is probably the thing to do.

2008-09-13
Jim

Solution

 38
$(dirname "$(readlink -f "$BASH_SOURCE")")
2011-07-27
test11

Solution

 30

This is Linux specific, but you could use:

SELF=$(readlink /proc/$$/fd/255)
2008-09-16
Steve Baker

Solution

 27

Here is a POSIX compliant one-liner:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH
2013-03-06
lamawithonel

Solution

 27

The shortest and most elegant way to do this is:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

This would work on all platforms and is super clean.

More details can be found in "Which directory is that bash script in?".

2019-06-20
Atul

Solution

 26

For Python, see my other answer here.

For Bash, see below:

Summary:

FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"

# OR, if you do NOT need it to work for **sourced** scripts too:
# FULL_PATH_TO_SCRIPT="$(realpath "$0")"

# OR, depending on which path you want, in case of nested `source` calls
# FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[0]}")"

# OR, add `-s` to NOT expand symlinks in the path:
# FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

Details:

How to obtain the full file path, full directory, and base filename of any script being run OR sourced...

...even when the called script is called from within another bash function or script, or when nested sourcing is being used!

For many cases, all you need to acquire is the full path to the script you just called. This can be easily accomplished using realpath. Note that realpath is part of GNU coreutils. If you don't have it already installed (it comes default on Ubuntu), you can install it with sudo apt update && sudo apt install coreutils.

get_script_path.sh (for the latest version of this script, see get_script_path.sh in my eRCaGuy_hello_world repo):

#!/bin/bash

# A. Obtain the full path, and expand (walk down) symbolic links
# A.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.
# FULL_PATH_TO_SCRIPT="$(realpath "$0")"
# A.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even
# if the script is called from within another bash function!
# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use
# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.
FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"
# B.1. `"$0"` works only if the file is **run**, but NOT if it is **sourced**.
# FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "$0")"
# B.2. `"${BASH_SOURCE[-1]}"` works whether the file is sourced OR run, and even
# if the script is called from within another bash function!
# NB: if `"${BASH_SOURCE[-1]}"` doesn't give you quite what you want, use
# `"${BASH_SOURCE[0]}"` instead in order to get the first element from the array.
FULL_PATH_TO_SCRIPT_KEEP_SYMLINKS="$(realpath -s "${BASH_SOURCE[-1]}")"

# You can then also get the full path to the directory, and the base
# filename, like this:
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"

# Now print it all out
echo "FULL_PATH_TO_SCRIPT = \"$FULL_PATH_TO_SCRIPT\""
echo "SCRIPT_DIRECTORY    = \"$SCRIPT_DIRECTORY\""
echo "SCRIPT_FILENAME     = \"$SCRIPT_FILENAME\""

IMPORTANT note on nested source calls: if "${BASH_SOURCE[-1]}" above doesn't give you quite what you want, try using "${BASH_SOURCE[0]}" instead. The first (0) index gives you the first entry in the array, and the last (-1) index gives you the last last entry in the array. Depending on what it is you're after, you may actually want the first entry. I discovered this to be the case when I sourced ~/.bashrc with . ~/.bashrc, which sourced ~/.bash_aliases with . ~/.bash_aliases, and I wanted the realpath (with expanded symlinks) to the ~/.bash_aliases file, NOT to the ~/.bashrc file. Since these are nested source calls, using "${BASH_SOURCE[0]}" gave me what I wanted: the expanded path to ~/.bash_aliases! Using "${BASH_SOURCE[-1]}", however, gave me what I did not want: the expanded path to ~/.bashrc.

Example command and output:

  1. Running the script:
    ~/GS/dev/eRCaGuy_hello_world/bash$ ./get_script_path.sh 
    FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh"
    SCRIPT_DIRECTORY    = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash"
    SCRIPT_FILENAME     = "get_script_path.sh"
    
  2. Sourcing the script with . get_script_path.sh or source get_script_path.sh (the result is the exact same as above because I used "${BASH_SOURCE[-1]}" in the script instead of "$0"):
    ~/GS/dev/eRCaGuy_hello_world/bash$ . get_script_path.sh 
    FULL_PATH_TO_SCRIPT = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash/get_script_path.sh"
    SCRIPT_DIRECTORY    = "/home/gabriel/GS/dev/eRCaGuy_hello_world/bash"
    SCRIPT_FILENAME     = "get_script_path.sh"
    

If you use "$0" in the script instead of "${BASH_SOURCE[-1]}", you'll get the same output as above when running the script, but this undesired output instead when sourcing the script:

~/GS/dev/eRCaGuy_hello_world/bash$ . get_script_path.sh 
FULL_PATH_TO_SCRIPT               = "/bin/bash"
SCRIPT_DIRECTORY                  = "/bin"
SCRIPT_FILENAME                   = "bash"

And, apparently if you use "$BASH_SOURCE" instead of "${BASH_SOURCE[-1]}", it will not work if the script is called from within another bash function. So, using "${BASH_SOURCE[-1]}" is therefore the best way to do it, as it solves both of these problems! See the references below.

Difference between realpath and realpath -s:

Note that realpath also successfully walks down symbolic links to determine and point to their targets rather than pointing to the symbolic link. If you do NOT want this behavior (sometimes I don't), then add -s to the realpath command above, making that line look like this instead:

# Obtain the full path, but do NOT expand (walk down) symbolic links; in
# other words: **keep** the symlinks as part of the path!
FULL_PATH_TO_SCRIPT="$(realpath -s "${BASH_SOURCE[-1]}")"

This way, symbolic links are NOT expanded. Rather, they are left as-is, as symbolic links in the full path.

The code above is now part of my eRCaGuy_hello_world repo in this file here: bash/get_script_path.sh. Reference and run this file for full examples both with and withOUT symlinks in the paths. See the bottom of the file for example output in both cases.

References:

  1. How to retrieve absolute path given relative
  2. taught me about the BASH_SOURCE variable: Unix & Linux: determining path to sourced shell script
  3. taught me that BASH_SOURCE is actually an array, and we want the last element from it for it to work as expected inside a function (hence why I used "${BASH_SOURCE[-1]}" in my code here): Unix & Linux: determining path to sourced shell script
  4. man bash --> search for BASH_SOURCE:

    BASH_SOURCE

    An array variable whose members are the source filenames where the corresponding shell function names in the FUNCNAME array variable are defined. The shell function ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}.

See also:

  1. My answer for Python: How do I get the path and name of the python file that is currently executing?
  2. [my answer] Unix & Linux: determining path to sourced shell script
2020-02-10
Gabriel Staples

Solution

 22
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`
2008-09-13
Monkeyboy

Solution

 20

Try using:

real=$(realpath "$(dirname "$0")")
2012-02-06
DarkPark

Solution

 19

I tried all of these and none worked. One was very close, but it had a tiny bug that broke it badly; they forgot to wrap the path in quotation marks.

Also a lot of people assume you're running the script from a shell, so they forget when you open a new script it defaults to your home.

Try this directory on for size:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

This gets it right regardless how or where you run it:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

So to make it actually useful, here's how to change to the directory of the running script:

cd "`dirname "$0"`"
2010-09-09
Mike Bethany

Solution

 19

Here is the simple, correct way:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

Explanation:

  • ${BASH_SOURCE[0]} - the full path to the script. The value of this will be correct even when the script is being sourced, e.g. source <(echo 'echo $0') prints bash, while replacing it with ${BASH_SOURCE[0]} will print the full path of the script. (Of course, this assumes you're OK taking a dependency on Bash.)

  • readlink -f - Recursively resolves any symlinks in the specified path. This is a GNU extension, and not available on (for example) BSD systems. If you're running a Mac, you can use Homebrew to install GNU coreutils and supplant this with greadlink -f.

  • And of course dirname gets the parent directory of the path.

2016-02-19
James Ko

Solution

 18

This is a slight revision to the solution e-satis and 3bcdnlklvc04a pointed out in their answer:

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}

This should still work in all the cases they listed.

This will prevent popd after a failed pushd. Thanks to konsolebox.

2010-04-13
Fuwjax

Solution

 17

I would use something like this:

# Retrieve the full pathname of the called script
scriptPath=$(which $0)

# Check whether the path is a link or not
if [ -L $scriptPath ]; then

    # It is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # Otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi
2012-11-21
Nicolas

Solution

 17

For systems having GNU coreutils readlink (for example, Linux):

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

There's no need to use BASH_SOURCE when $0 contains the script filename.

2014-05-28
user1338062

Solution

 14

$_ is worth mentioning as an alternative to $0. If you're running a script from Bash, the accepted answer can be shortened to:

DIR="$( dirname "$_" )"

Note that this has to be the first statement in your script.

2011-09-16
hurrymaplelad

Solution

 14

Incredible how simple can be, no matter how you call the script:

#!/bin/bash
#

the_source=$(readlink -f ${BASH_SOURCE[0]})
the_dirname=$(dirname ${the_source})

echo "the_source: ${the_source}"
echo "the_dirname: ${the_dirname}"

Run from anywhere:

user@computer:~/Downloads/temp$ ./test.sh 

Output:

the_source: /home/user/Downloads/temp/test.sh
the_dirname: /home/user/Downloads/temp
2023-04-03
scrat.squirrel

Solution

 13

This works in Bash 3.2:

path="$( dirname "$( which "$0" )" )"

If you have a ~/bin directory in your $PATH, you have A inside this directory. It sources the script ~/bin/lib/B. You know where the included script is relative to the original one, in the lib subdirectory, but not where it is relative to the user's current directory.

This is solved by the following (inside A):

source "$( dirname "$( which "$0" )" )/lib/B"

It doesn't matter where the user is or how he/she calls the script. This will always work.

2008-10-14
Matt Tardiff

Solution

 13

These are short ways to get script information:

Folders and files:

    Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

Using these commands:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

And I got this output:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

Also see: https://pastebin.com/J8KjxrPF

2018-03-09
User8461

Solution

 10

I've compared many of the answers given, and came up with some more compact solutions. These seem to handle all of the crazy edge cases that arise from your favorite combination of:

  • Absolute paths or relative paths
  • File and directory soft links
  • Invocation as script, bash script, bash -c script, source script, or . script
  • Spaces, tabs, newlines, Unicode, etc. in directories and/or filename
  • Filenames beginning with a hyphen

If you're running from Linux, it seems that using the proc handle is the best solution to locate the fully resolved source of the currently running script (in an interactive session, the link points to the respective /dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

This has a small bit of ugliness to it, but the fix is compact and easy to understand. We aren't using bash primitives only, but I'm okay with that because readlink simplifies the task considerably. The echo X adds an X to the end of the variable string so that any trailing whitespace in the filename doesn't get eaten, and the parameter substitution ${VAR%X} at the end of the line gets rid of the X. Because readlink adds a newline of its own (which would normally be eaten in the command substitution if not for our previous trickery), we have to get rid of that, too. This is most easily accomplished using the $'' quoting scheme, which lets us use escape sequences such as \n to represent newlines (this is also how you can easily make deviously named directories and files).

The above should cover your needs for locating the currently running script on Linux, but if you don't have the proc filesystem at your disposal, or if you're trying to locate the fully resolved path of some other file, then maybe you'll find the below code helpful. It's only a slight modification from the above one-liner. If you're playing around with strange directory/filenames, checking the output with both ls and readlink is informative, as ls will output "simplified" paths, substituting ? for things like newlines.

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"
2013-04-21
billyjmc

Solution

 10

Try the following cross-compatible solution:

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

As the commands such as realpath or readlink could be not available (depending on the operating system).

Note: In Bash, it's recommended to use ${BASH_SOURCE[0]} instead of $0, otherwise path can break when sourcing the file (source/.).

Alternatively you can try the following function in Bash:

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

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

Related:

2013-11-28
kenorb