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

76

6246

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
Barela answered 12/9, 2008 at 20:40 Comment(4)
None of the current solutions work if there are any newlines at the end of the directory name - They will be stripped by the command substitution. To work around this you can append a non-newline character inside the command substitution - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)" - and remove it without a command substitution - DIR="${DIR%x}".Japhetic
@jpmc26 There are two very common situations: Accidents and sabotage. A script shouldn't fail in unpredictable ways just because someone, somewhere, did a mkdir $'\n'.Japhetic
anyone who lets people sabotage their system in that way shouldn't leave it up to bash to detect such problems... much less hire people capable of making that kind of mistake. I have never had, in the 25 years of using bash, seen this kind of thing happen anywhere.... this is why we have things like perl and practices such as taint checking (i will probably be flamed for saying that :)Kerchief
I stronly suggest to read this Bash FAQ about the subject.Gloat
I
8089
#!/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'
Irretentive answered 12/9, 2008 at 20:40 Comment(16)
You can fuse this approach with the answer by user25866 to arrive at a solution that works with source <script> and bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".Overcritical
Sometimes cd prints something to STDOUT! E.g., if your $CDPATH has .. To cover this case, use DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"Ocd
This accepted answer is not ok, it doesn't work with symlinks and is overly complex. dirname $(readlink -f $0) is the right command. See gist.github.com/tvlooy/cbfbdb111a4ebad8b93e for a testcaseAldosterone
@Aldosterone IMO your answer isn't exactly OK as-is either, because it fails when there is a space in the path. In contrast to a newline character, this isn't unlikely or even uncommon. dirname "$(readlink -f "$0")" doesn't add complexity and is fair measure more robust for the minimal amount of trouble.Patchwork
@Aldosterone dirname "$(readlink -f "$0")" fails though in the case where the script is sourced, eg: /bin/bash -c . script.sh... use $(dirname "$(readlink -f "${BASH_SOURCE[0]}")") insteadWheelbarrow
@Faither I’ve rolled back all your changes since they didn’t actually improve the answer at all — they just added clutter. Please don’t perform purely stylistic edits unless they lead to a substantial, objective improvement. With as many edits as you made, there’s also a risk of introducing errors (which in fact happened, and you noticed at least one error yourself and had reverted that).Eulaheulalee
How is readlink "$link" vs readlink -e -- "$link" a "clutter" or "purely stylistic edits"?Fixation
in zsh, simply using this worked for me: SCRIPT_DIR="${0:A:h}"Hemeralopia
A small crit which might make a small difference for very long symlink chains: inside the loop, you don't need to fully resolve DIR: DIR=$(dirname "$SOURCE") will do. At that point, DIR can be an unholy mess of relative paths, because you're going to sort it out with cd -P at the end.Precarious
It doesn't seems to work if I run the script via SSH. When I run ssh user@host 'bash -s < ~/path/to/script.sh' I get the home of the user as result /home/user instead of /home/user/path/to/.Migratory
@ndr4: your exemple works correctly: bash -s < somescript : starts bash and executes commandes read from stdin (here, content of the script) : the actuel program launched is indeed your login bash, and not the script itself : you get infos of your login bash (which is launched as : "bash" via $PATH, and "bash" has "." as directory, and as you didn't cd you get . translated to the homedir.Featheredge
What "--" is used for ? I tested dirname -- "${BASH_SOURCE[0]}" dirname "${BASH_SOURCE[0]}" and it was doing the same. I read that '--' means no more arguments but it's clearly not the case so why ?Rejoinder
See this section on shell built-ins. gnu.org/savannah-checkouts/gnu/bash/manual/….Duala
Also pubs.opengroup.org/onlinepubs/9699919799/basedefs/… (Guideline 10) "The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the '-' character."Duala
Could you please clarify this statement by example (test case): "It will work as long as the last component of the path used to find the script is not a symlink"? I tried making both (A) dirname and (B) basename of the script a symlink, but it still works as expected: ./L0_link/L1_link/L2_link/script.bash Is this statement already outdated?Semipalatinsk
Please retain the one liner by using -f to readline, I forget it all the time, and this is the proposed solution: SCRIPT_DIR="$( cd -P "$( dirname "$(readlink -f "${BASH_SOURCE[0]}")" )" >/dev/null 2>&1 && pwd )"Heritage
C
1238

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]
Cauterant answered 12/9, 2008 at 20:40 Comment(9)
For portability beyond bash, $0 may not always be enough. You may need to substitute "type -p $0" to make this work if the command was found on the path.Motch
@Darron: you can only use type -p if the script is executable. This can also open a subtle hole if the script is executed using bash test2.sh and there is another script with the same name executable somewhere else.Disown
@Darron: but since the question is tagged bash and the hash-bang line explicitly mentions /bin/bash I'd say it's pretty safe to depend on bashisms.Glennieglennis
+1, but the problem with using dirname $0 is that if the directory is the current directory, you'll get .. That's fine unless you're going to change directories in the script and expect to use the path you got from dirname $0 as though it were absolute. To get the absolute path: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (But surely there's a better way to expand that path?)Persecution
@T.J. Crowder I'm not sure sure dirname $0 is a problem if you assign it to a variable and then use it to launch a script like $dir/script.sh; I would imagine this is the use case for this type of thing 90% of the time. ./script.sh would work fine.Cauterant
@matt b: As I said, it's fine as long as your script using it doesn't change the current directory. If your script does, then you try to launch ./script.sh and script.sh is meant to be in the same directory as the original, it's going to fail because you're no longer there. See also: stackoverflow.com/questions/4774054/…Persecution
"readlink -e" would resolve the discrepancy when executed in the current directory. It should resolve to the absolute path instead of "."Effie
#!/bin/bash cd $(dirname "$0") python manage.py runserver 0.0.0.0:5000 So now from wherever I run the entrypoint.sh, it always works.Pyroligneous
You seem to have a typo in your example. I assume that for the second test the script still resides in /home/matt but you are executing a script that resides in the current directory, which is /tmp so the output would be different than is shown. You probably need to change the line that executes the script the second time from './test.sh' to '/home/matt/test.sh'.Bridgettbridgette
S
675

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
Surrender answered 12/9, 2008 at 20:40 Comment(14)
readlink will not availabe in some platform in default installation. Try to avoid using it if you canTalie
be careful to quote everything to avoid whitespace issues: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"Redmond
In OSX Yosemite 10.10.1 -f is not recognised as an option to readlink. Using stat -f instead does the job. ThanksEscritoire
In OSX, there is greadlink, which is basically the readlink we are all familiar. Here is a platform independent version: dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`Donatelli
Good call, @robert. FYI, greadlink can easily be installed through homebrew: brew install coreutilsSurrender
Note that $0 doesn't work if the file is sourced. You get -bash instead of the script name.Placia
In OSX, you can use realpath or grealpath, which will give you the resolved path from the root of the file system. Example: cd /usr/bin; realpath ls /usr/bin/lsAlberto
@Talie readlink is part of the coreutils package which contains the base GNU utilities. No idea why it wouldn't be available on a GNU-based system. I think we can safely assume OP is using GNU, since the question is about GNU Bash?Loxodrome
IMHO, this is the most accurate answer. If you don't have readlink, one can use realpath too. Be aware only to use canonical output if needed => readlink -fCinchonine
The command readlink is in the coreutils packages which is part of any standard Linux distribution, so I think 'dirname $(readlink -f "$0") is the more general and simple solution which doen't depends on how the script was executed (absolute vs relative paths)Shilohshim
@svvac, I get path/name of the script where source word is used and that is what I wanted. How did you get -bash?Muddle
$BASH_SOURCE works properly in my .bashrc while $0 does notMontana
This works well, except when sourced as other comments also pointed out. Try . ~/whatdir.sh to see that $0 is -bash or bash in that case.Giese
@DarioSeidl : this is normal: when sourcing a script, you are NOT executing that script, you are telling the current shell (bash or -bash) to execute the same instructions as the ones contained in the scriptFeatheredge
C
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.

Curiel answered 12/9, 2008 at 20:40 Comment(7)
Nice! Could be made shorter replacing "pushd[...] popd /dev/null" by SCRIPT_PATH=readlink -f $(dirname "${VIRTUAL_ENV}");Jann
This is by far the most "stable" version I've seen. Thank you!Gaul
Should not the colon in the line 1 be moved in line two between the if- and then-statements?Luellaluelle
And instead of using pushd ...; would not it be better to use $(cd dirname "${SCRIPT_PATH}" && pwd)? But anyway great script!Luellaluelle
It's dangerous for a script to cd out of its current directory in the hope of cding back again later: The script may not have permission to change directory back to the directory that was current when it was invoked. (Same goes for pushd/popd)Vivica
readlink -f is GNU-specific. BSD readlink does not have that option.Mauk
dotfiles_public [dotfiles_except_crontab_and_autostart] folder name caused cd dirname "$SCRIPT_PATH"`` to fail with cd: too many argumentsMontana
M
148

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
Monitorial answered 12/9, 2008 at 20:40 Comment(9)
Or, more obscurely, on one line: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")Glyceric
Why isn't this the accepted answer? Is there any difference using realpath from resolving "manually" with a loop of readlink? Even the readlink man page says Note realpath(1) is the preferred command to use for canonicalization functionality.Osis
And by the way shouldn't we apply realpath before dirname, not after? If the script file itself is a symlink... It would give something like DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Actually very close to the answer proposed by Simon.Osis
@Osis I think the accept one is try to be compatible with all popular shell/distro. More over, depending on what you are trying to do, in most cases people want to obtain the directory where the symlink located instead of the directory of the actual source.Counterinsurgency
The only reason is missing coreutils on mac. I am using SCRIPT=$(realpath "${BASH_SOURCE[0]}") + DIR=$(dirname "$SCRIPT").Trow
My realpath (and man7.org/linux/man-pages/man1/realpath.1.html ) don't have the -f option; is that supposed to be -e @vaeVictis ? (That user added it.)Entelechy
@AaronD.Marasco You are correct about the option, it is -e . I did not add it, it was another user. You could modify the message yourself, I would not be offended :) Thanks.Gulgee
@Gulgee done - I mis-read the edit logs its counterintuitive it shows the editor at the top of the diff, unlike the answers on the site where it's the bottom.Entelechy
@AaronD.Marasco Thanks! Now it is correct and shining! The position tricked me too and I had to read a couple of times indeed. See you in the bit fieldsGulgee
P
144

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.

Pocketknife answered 12/9, 2008 at 20:40 Comment(2)
When I do ./foo/script, then $(dirname $BASH_SOURCE) is ./foo.Reprimand
@Till, In this case we can use realpath command to get full path of ./foo/script. So dirname $(realpath ./foo/script) will give the path of script.Cabman
E
126

Short answer:

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

or (preferably):

"$( dirname -- "$0"; )"
Excellency answered 12/9, 2008 at 20:40 Comment(7)
It won't work if you source the script. "source my/script.sh"Grossman
I use this all the time in my bash scripts that automate stuff and often invoke other scripts in the same dir. I'd never use source on these and cd $(dirname $0) is easy to remember.Falk
@vidstige: ${BASH_SOURCE[0]} instead of $0 will work with source my/script.shEnduring
@TimothyJones that will fail 100% of the time if sourced from any other shell than bash. ${BASH_SOURCE[0]} is not satisfactory at all. ${BASH_SOURCE:-0} is much better.Indoctrinate
@MathieuCAROFF ${BASH_SOURCE:-0} will result 0 not $0 in shell otherthan bash eg shAlita
@TimothyJones $BASH_SOURCE not avail. in sh shellAlita
@NamGVU Yes, that's right. This question is about Bash, thoughEnduring
P
121

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.

Precincts answered 12/9, 2008 at 20:40 Comment(8)
with your solution, invoking the script like ./script.sh shows . instead of the full directory pathDragging
There's no -f option for readlink on MacOS. Use stat instead. But still, it shows . if you are in 'this' dir.Oneirocritic
You need to install coreutils from Homebrew and use greadlink to get the -f option on MacOS because it is *BSD under the covers and not Linux.Inch
You should add double quotes surrounding all the right hand side: DIR="$(dirname "$(readlink -f "$0")")"Azarria
Using realpath instead of readlink -f works on both Linux and macOS (BSD): dir="$(dirname "$(realpath "$0")")"Ameeameer
@MarkGates that's wrong, I got realpath: command not found on vanilla macOS, I suppose you have installed some homebrew package to get it working on your macCrawford
@Crawford You're right – I see realpath came from brew coreutils, which also includes GNU readlink as greadlink. Nonetheless, GNU readlink's docs say "Note the realpath command is the preferred command to use for canonicalization." And realpath appears to be in other BSD.Ameeameer
$BASH_SOURCE works properly in my .bashrc while $0 does notMontana
C
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.

Carob answered 12/9, 2008 at 20:40 Comment(2)
You can do it all in one line like this: DIRECTORY=$(cd dirname $0 && pwd)Trifolium
This doesn't work if the script sources another script and you want to know the name of the latter.Bogor
G
43
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
Gensler answered 12/9, 2008 at 20:40 Comment(2)
As many of the previous answers explain in detail, neither $0 nor pwd are guaranteed to have the right information, depending on how the script is invoked.Corruption
@IMSoP, wrong. "many of the previous answers" explains that pwd returns the current directory, in this case the directory changed before the call. As for $0, in most cases it is desired to use $0 instead of $BASH_SOURCE, eg. if script sourcing a library that needs do determine full path of the script (not library).Shostakovich
D
42

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

DIR=$(cd "$(dirname "$0")"; pwd)
Diastase answered 12/9, 2008 at 20:40 Comment(1)
so this doesn't work on Linux?Crawford
S
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.

Shortcircuit answered 12/9, 2008 at 20:40 Comment(2)
If you intend to disprove his comment, PROVE that a script CAN access where it's stored with a code example.Heartstrings
Did you tried it ? $0 does have the full path information for third case.Shostakovich
C
39
$(dirname "$(readlink -f "$BASH_SOURCE")")
Chuch answered 12/9, 2008 at 20:40 Comment(2)
I prefer, $BASH_SOURCE over $0 , because it's explicit even for readers not well-versed in bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")Indiscreet
Also, $BASH_SOURCE works while $0 does not in case of my .bashrc (where both symlink AND sourcing is also used)Montana
S
29

This is Linux specific, but you could use:

SELF=$(readlink /proc/$$/fd/255)
Summation answered 12/9, 2008 at 20:40 Comment(2)
It's also bash specific, but perhaps bash's behavior has changed? /proc/fd/$$/255 seems to point to the tty, not to a directory. For example, in my current login shell, file descriptors 0, 1, 2, and 255 all refer to /dev/pts/4. In any case, the bash manual doesn't mention fd 255, so it's probably unwise to depend on this behavior.\Automat
Interactive shell != script. Anyway realpath ${BASH_SOURCE[0]}; would seem to be the best way to go.Summation
N
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?".

Nerissanerita answered 12/9, 2008 at 20:40 Comment(1)
great clean solution, but this will not work if the file is symlinked.Jordan
O
27

Here is a POSIX compliant one-liner:

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

# test
echo $SCRIPT_PATH
Opiate answered 12/9, 2008 at 20:40 Comment(2)
I had success with this when running a script by itself or by using sudo, but not when calling source ./script.shMcleroy
And it fails when cd is configured to print the new path name.Pardo
D
25

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
Demonstrative answered 10/2, 2020 at 19:46 Comment(3)
What's the difference between ${BASH_SOURCE[-1]} and ${BASH_SOURCE[0]} ? I know -1 retrieves the last element from the array and 0 retrieves the first one but in which case do I want to use one over the other ?Demonology
@ElieG., See my IMPORTANT note on nested source calls section in the answer. It has to do with nested sourcing, when one script which you source sources another script.Demonstrative
@ElieG., I think so. Also, I didn't try it, but in my case I think index 1 would have given me the same result as -1 since I believe the array only had 2 elements in it, so that would have been the last element in both cases.Demonstrative
P
22
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`
Paramnesia answered 12/9, 2008 at 20:40 Comment(1)
I haven't tested it across different systems. But this solution is the one that works right away at least on Ubuntu, for me!Zenaidazenana
C
20

Try using:

real=$(realpath "$(dirname "$0")")
Curie answered 12/9, 2008 at 20:40 Comment(7)
All I want to know is, why this way is not good? It seemed no bad and correct for me. Could anyone explain why it's downvoted?Pitre
realpath is not a standard utility.Peat
On Linux, realpath is a standard utility (part of the GNU coreutils package), but it is not a bash built-in (i.e., a function provided by bash itself). If you're running Linux, this method will probably work, although I'd substitute the $0 for ${BASH_SOURCE[0]} so that this method will work anywhere, including in a function.Calves
The order of the operations in this answer is wrong. You need to first resolve the symlink, then do dirname because the last part of $0 may be a symlink that points to a file that is not in the same directory as the symlink itself. The solution described in this answer just gets the path of the directory where the symlink it stored, not the directory of the target. Furthermore, this solution is missing quoting. It will not work if the path contains special characters.Azarria
This solution "just gets the path of the directory where the symlink it stored", and that's what I actually want. PS: quoting still should be added.Whangee
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"Whangee
SCRIPT=`realpath $0`Coolish
B
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.

Bataan answered 12/9, 2008 at 20:40 Comment(1)
greadlink -f unfortunately doesn't work effectively when sourceing the script on Mac :(Multinational
B
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"`"
Biquadratic answered 12/9, 2008 at 20:40 Comment(2)
Doesn't work if the script is being sourced from another script.Bogor
This does not work if the last part of $0 is a symbolic link pointing to an entry of another directory (ln -s ../bin64/foo /usr/bin/foo).Azarria
C
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.

Canonical answered 12/9, 2008 at 20:40 Comment(5)
This works perfectly to get the "real" dirname, rather than just the name of a symlink. Thank you!Romelda
Better SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }Delorsedelos
@konsolebox, what are you trying to defend against? I'm generally a fan of inlining logical conditionals, but what was the specific error that you were seeing in the pushd? I'd match rather find a way to handle it directly instead of returning an empty SCRIPT_DIR.Canonical
@Canonical Natural practice to avoid doing popd in cases (even when rare) where pushd fails. And in case pushd fails, what do you think should be the value of SCRIPT_DIR? The action may vary depending on what may seem logical or what one user could prefer but certainly, doing popd is wrong.Delorsedelos
All those pushd popd dangers could be avoided simply by dropping them and using cd + pwd enclosed in a command substitution instead. SCRIPT_DIR=$(...)Nicodemus
A
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.

Amesace answered 12/9, 2008 at 20:40 Comment(1)
unless the script was sourced with . or 'source' in which case it will still be whatever script sourced it, or, if from the command line, '-bash' (tty login) or 'bash' (invoked via 'bash -l') or '/bin/bash' (invoked as an interactive non-login shell)Kerchief
P
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
Paratroops answered 12/9, 2008 at 20:40 Comment(2)
This is the real one! Works with simple sh too! Problem with simple dirname "$0" based solutions: If the script is in the $PATH and is invoked without path, they will give wrong result.Unlisted
@Unlisted Not so. If the script is found via the PATH, $0 will contain the absolute filename. If the script is invoked with a relative or absolute filename containing a /, $0 will contain that.Irradiant
P
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
Pouched answered 12/9, 2008 at 20:40 Comment(1)
Upvoting this answer because it tells in which directory script really is, no mater if calling it from symlink or original script. It is especially useful when your script is just a symlink in .bin directory created by npm/yarn and you need to now original script location to relatively reference static resources bundled in your npm package...Spanishamerican
C
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.

Cragsman answered 12/9, 2008 at 20:40 Comment(2)
It breaks if you source or . the script. In those situations, $_ would contain the last parameter of the last command you ran before the .. $BASH_SOURCE works every time.Petrillo
This is Perl-like! A coincidence?Barela
M
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

Mozart answered 12/9, 2008 at 20:40 Comment(0)
W
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.

Wye answered 12/9, 2008 at 20:40 Comment(2)
The point on which is very debatable. type, hash, and other builtins do the same thing better in bash. which is kindof more portable, though it really isn't the same which used in other shells like tcsh, that has it as a builtin.Deception
"Always"? Not at all. which being an external tool, you have no reason to believe it behaves identically to the parent shell.Cyrillic
G
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:

Gruber answered 12/9, 2008 at 20:40 Comment(2)
@Chris realpath function takes 1 argument. If argument has already absolute path, print it as it is, otherwise print $PWD + filename (without ./ prefix).Gruber
Your cross-compatible solution doesn’t work when the script is symlinked.Uxmal
S
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"
Strunk answered 12/9, 2008 at 20:40 Comment(4)
I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop.Repentant
@DanDascalescu Using the one-liner? Or the full code snippet at the bottom? And were you feeding it any tricky pathnames?Strunk
The one line plus another line to echo $resolved, I saved it as d, chmod +x d, ./d.Repentant
@DanDascalescu The first line in your script needs to be #!/bin/bashStrunk
C
9

I believe I've got this one. I'm late to the party, but I think some will appreciate it being here if they come across this thread. The comments should explain:

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi
Capacity answered 12/9, 2008 at 20:40 Comment(0)
B
9

The best compact solution in my view would be:

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

There is no reliance on anything other than Bash. The use of dirname, readlink and basename will eventually lead to compatibility issues, so they are best avoided if at all possible.

Barela answered 12/9, 2008 at 20:40 Comment(4)
You probably should add slash to that: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". You'd have problems with root directory if you don't. Also why do you even have to use echo?Delorsedelos
dirname and basename are POSIX standardized, so why avoid using them? Links: dirname, basenameLynching
Preventing two extra process forks and sticking to shell built-ins may be one reason why.Nicodemus
You can call me purist - I have a somewhat similar version that doesn't involve subshells or forking anything: BIN=${0/#[!\/]/"$PWD/${0:0:1}"}; DIR=${BIN%/*}. First replace anything not starting by / (i.e. a relative path) with $PWD/ (we also have to append the first character we matched in the substitution). Then get the directory. You can finally cd $DIR if you wish, or use it in relative paths.Hotshot
E
9

Hmm, if in the path, basename and dirname are just not going to cut it and walking the path is hard (what if the parent didn't export PATH?!).

However, the shell has to have an open handle to its script, and in Bash the handle is #255.

SELF=`readlink /proc/$$/fd/255`

works for me.

Ennead answered 12/9, 2008 at 20:40 Comment(1)
I get /dev/pts/30 with bash on Ubuntu 14.10 Desktop, instead of the actual directory I run the script from.Repentant
U
8

This is, annoyingly, the only one-liner I've found that works on both Linux and macOS when the executable script is a symlink:

SCRIPT_DIR=$(python -c "import os; print(os.path.dirname(os.path.realpath('${BASH_SOURCE[0]}')))")

or, similarly, using python3 pathlib module:

SCRIPT_DIR=$(python3 -c "from pathlib import Path; print(Path('${BASH_SOURCE[0]}').resolve().parent)")

Tested on Linux and macOS and compared to other solutions in this gist: https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371

Uncovered answered 12/9, 2008 at 20:40 Comment(0)
U
8

You can do that just combining the script name ($0) with realpath and/or dirname. It works for Bash and Shell.

#!/usr/bin/env bash

RELATIVE_PATH="${0}"
RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"

echo "RELATIVE_PATH->${RELATIVE_PATH}<-"
echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"
echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"
echo "FULL_PATH->${FULL_PATH}<-"

The output will be something like this:

# RELATIVE_PATH->./bin/startup.sh<-
# RELATIVE_DIR_PATH->./bin<-
# FULL_DIR_PATH->/opt/my_app/bin<-
# FULL_PATH->/opt/my_app/bin/startup.sh<-

$0 is the name of the script itself

4.4. Special Variable Types

An example: LozanoMatheus/get_script_paths.sh

Unison answered 12/9, 2008 at 20:40 Comment(0)
J
7

None of the current solutions work if there are any newlines at the end of the directory name - They will be stripped by the command substitution. To work around this you can append a non-newline character inside the command substitution and then strip just that character off:

dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd && echo x)"
dir="${dir%x}"

This protects against two very common situations: Accidents and sabotage. A script shouldn't fail in unpredictable ways just because someone, somewhere, did a mkdir $'\n'.

Japhetic answered 12/9, 2008 at 20:40 Comment(0)
F
7

The top response does not work in all cases...

As I had problems with the BASH_SOURCE with the included 'cd' approach on some very fresh and also on less fresh installed Ubuntu 16.04 (Xenial Xerus) systems when invoking the shell script by means of "sh my_script.sh", I tried out something different that as of now seems to run quite smoothly for my purposes. The approach is a bit more compact in the script and is further much lesser cryptic feeling.

This alternate approach uses the external applications 'realpath' and 'dirname' from the coreutils package. (Okay, not anyone likes the overhead of invoking secondary processes - but when seeing the multi-line scripting for resolving the true object it won't be that bad either having it solve in a single binary usage.)

So let’s see one example of those alternate solution for the described task of querying the true absolute path to a certain file:

PATH_TO_SCRIPT=`realpath -s $0`
PATH_TO_SCRIPT_DIR=`dirname $PATH_TO_SCRIPT`

But preferably you should use this evolved version to also support the use of paths with spaces (or maybe even some other special characters):

PATH_TO_SCRIPT=`realpath -s "$0"`
PATH_TO_SCRIPT_DIR=`dirname "$PATH_TO_SCRIPT"`

Indeed, if you don’t need the value of the SCRIPT variable then you might be able to merge this two-liner into even a single line. But why really shall you spend the effort for this?

Fanestil answered 12/9, 2008 at 20:40 Comment(2)
This question is bash specific. If you invoke a script with sh, the shell might be something else, such as zsh or dash.Retention
i wont check its code now - but you can invoke it with "bash" if you want. see "sh" just as an alias for the binary based selection of the compatible shell executor.Fanestil
R
6

Yet another variant:

SELF=$(SELF=$(dirname "$0") && bash -c "cd \"$SELF\" && pwd")
echo "$SELF"

This works on macOS as well, determines the canonical path, and does not change the current directory.

Rend answered 12/9, 2008 at 20:40 Comment(1)
In bash that produces the following error: dirname: invalid option -- 'bFreeliving
I
6

This worked for me when the other answers here did not:

thisScriptPath=`realpath $0`
thisDirPath=`dirname $thisScriptPath`
echo $thisDirPath
Intercrop answered 12/9, 2008 at 20:40 Comment(3)
Note that there's no ned to resort to echo. Simply invoking dirname will print the directory name. And, in this case, invoking echo without properly quoting the variable will potentially produce different output, since it will squash whitespace. Overall, cleaner to simply write dirname "$(realpath "$0")"Bureaucracy
You are correct that this can be compressed into a single line. The breakdown into separate variables is for illustrative / self documenting purposes. The echo line is just there as p.o.c.Intercrop
I'm pretty sure it does (if I remember correctly). Note that different nix distros and contexts like you bring up (and say Linux, vs Mac, vs Cygwin...bash vs dash...) handle all of this somewhat differently. Hence the reason there are SOO many answers on this thread! You'll need to test your use case to confirm. I wish there were a solid, cross platform/context answer to this simple question!Intercrop
H
6

Use a combination of readlink to canonicalize the name (with a bonus of following it back to its source if it is a symlink) and dirname to extract the directory name:

script="`readlink -f "${BASH_SOURCE[0]}"`"
dir="`dirname "$script"`"
Heterophyte answered 12/9, 2008 at 20:40 Comment(0)
D
6

None of these other answers worked for a Bash script launched by Finder in OS X. I ended up using:

SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
sed s:.*/Volumes/:/Volumes/:`"

It is not pretty, but it gets the job done.

Department answered 12/9, 2008 at 20:40 Comment(0)
F
5

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

Faeroese answered 12/9, 2008 at 20:40 Comment(1)
Works great! Tested on both linux and macos. Added to unit-test gist with various other solutions: gist.github.com/ptc-mrucci/…Uncovered
A
5

I usually use:

dirname $(which $BASH_SOURCE)
Adviser answered 12/9, 2008 at 20:40 Comment(0)
S
5

This is the only way I've found to tell reliably:

SCRIPT_DIR=$(dirname $(cd "$(dirname "$BASH_SOURCE")"; pwd))
Sonnysonobuoy answered 12/9, 2008 at 20:40 Comment(1)
this is giving me the directory with the last entry stripped off, i.e., the path to the container of the container of the script.Lear
H
4

One advantage of this method is that it doesn't involve anything outside Bash itself and does not fork any subshell neither.

First, use pattern substitution to replace anything not starting with / (i.e., a relative path) with $PWD/. Since we use a substitution to match the first character of $0, we also have to append it back (${0:0:1} in the substitution).

Now we have a full path to the script; we can get the directory by removing the last / and anything the follows (i.e., the script name). That directory can then be used in cd or as a prefix to other paths relative to your script.

#!/bin/bash

BIN=${0/#[!\/]/"$PWD/${0:0:1}"}
DIR=${BIN%/*}

cd "$DIR"

If your script may be sourced rather than executed, you can of course replace $0 with ${BASH_SOURCE[0]}, such as:

BIN=${BASH_SOURCE[0]/#[!\/]/"$PWD/${BASH_SOURCE[0]:0:1}"}

This will work for executable scripts too. It's longer, but more polyvalent.

Hotshot answered 12/9, 2008 at 20:40 Comment(2)
Unless I'm missing something the ${BASH_SOURCE[0]%/*} seems sufficient?Liebowitz
@Liebowitz Not if you have . in your $PATH and are executing the script from the current directory using that. Although I loathe having . in $PATH it's the edge case that makes this so complicated...Hotshot
S
4

Here's a command that works under either Bash or zsh, and whether executed stand-alone or sourced:

[ -n "$ZSH_VERSION" ] && this_dir=$(dirname "${(%):-%x}") \
    || this_dir=$(dirname "${BASH_SOURCE[0]:-$0}")

How it works

The zsh current file expansion: ${(%):-%x}

${(%):-%x} in zsh expands to the path of the currently-executing file.

The fallback substitution operator :-

You know already that ${...} substitutes variables inside of strings. You might not know that certain operations are possible (in both Bash and zsh) on the variables during substitution, like the fallback expansion operator :-:

% x=ok
% echo "${x}"
ok

% echo "${x:-fallback}"
ok

% x=
% echo "${x:-fallback}"
fallback

% y=yvalue
% echo "${x:-$y}"
yvalue

The %x prompt escape code

Next, we'll introduce prompt escape codes, a zsh-only feature. In zsh, %x will expand to the path of the file, but normally this is only when doing expansion for prompt strings. To enable those codes in our substitution, we can add a (%) flag before the variable name:

% cat apath/test.sh
fpath=%x
echo "${(%)fpath}"

% source apath/test.sh
apath/test.sh

% cd apath
% source test.sh
test.sh

An unlikely match: the percent escape and the fallback

What we have so far works, but it would be tidier to avoid creating the extra fpath variable. Instead of putting %x in fpath, we can use :- and put %x in the fallback string:

% cat test.sh
echo "${(%):-%x}"

% source test.sh
test.sh

Note that we normally would put a variable name between (%) and :-, but we left it blank. The variable with a blank name can't be declared or set, so the fallback is always triggered.

Finishing up: what about print -P %x?

Now we almost have the directory of our script. We could have used print -P %x to get the same file path with fewer hacks, but in our case, where we need to pass it as an argument to dirname, that would have required the overhead of a starting a new subshell:

% cat apath/test.sh
dirname "$(print -P %x)"  # $(...) runs a command in a new process
dirname "${(%):-%x}"

% source apath/test.sh
apath
apath

It turns out that the hacky way is both more performant and succinct.

Sciomachy answered 12/9, 2008 at 20:40 Comment(0)
T
4

$0 is not a reliable way to get the current script path. For example, this is my .xprofile:

#!/bin/bash
echo "$0 $1 $2"
echo "${BASH_SOURCE[0]}"
# $dir/my_script.sh &

cd /tmp && ~/.xprofile && source ~/.xprofile

/home/puchuu/.xprofile
/home/puchuu/.xprofile
-bash
/home/puchuu/.xprofile

So please use BASH_SOURCE instead.

Trow answered 12/9, 2008 at 20:40 Comment(0)
S
3

Why isn't any answer using the command -v "$0" solution?

The command -v "$0" approach is POSIX compliant.(1)

FYI, none of the following three commands are POSIX compliant.

  • realpath is not POSIX compliant and may not be available everywhere.(2)
  • readlink is also not POSIX compliant.(3)
  • which is not POSIX compliant, as mentioned.(1)

TL;DR: The POSIX compliant solution


if ${BASH_SOURCE[0]} else $(command -v)

  1. If ${BASH_SOURCE[0]} is available, use it.
  2. If ${BASH_SOURCE[0]} is not available, use command -v "$0".

This one-liner, which is POSIX compliant, returns the canonical file path of the script itself:

SELF_PATH=$( readlinkf "${BASH_SOURCE[0]:-"$(command -v -- "$0")"}" )

or using the non-POSIX complant readlink -f:

SELF_PATH=$( readlink -f "${BASH_SOURCE[0]:-"$(command -v -- "$0")"}" )

Analyse


The solution for finding the actual path of a script itself should be divided into two steps:

  1. Obtain a correct path of the script.
  2. Resolve the path.

While many methods combine both steps into one, they are, in fact, addressing different problems.

Once the correct path of the script is obtained, whether it is an absolute path, a relative path, or a symlink, there are several methods available to resolve it to the canonical path.

Test environment


Setup / Reproduce:

$ mkdir /tmp/test_step01 ; cd $_
$ mkdir bin some_dir other_dir
$ touch 'bin/prog in path' 'other_dir/source a script'
$ ln 'bin/prog in path' 'some_dir/prog outside'
$ ln -s '../some_dir/prog outside' other_dir/ln_prog_outside
$ chmod u+x 'bin/prog in path' 'other_dir/source a script'
$ tree /tmp/test_step01
/tmp/test_step01
├── bin
│   └── prog in path
├── other_dir
│   ├── ln_prog_outside -> ../some_dir/prog outside
│   └── source a script
└── some_dir
    └── prog outside

ls and cat:

$ ( cd /tmp/test_step01 ; ls -Uion 'bin/prog in path' 'some_dir/prog outside' other_dir/ln_prog_outside 'other_dir/source a script' )
192 -rwxr--r-x 2 1000 342 Dec 15 06:29 'bin/prog in path'
192 -rwxr--r-x 2 1000 342 Dec 15 06:29 'some_dir/prog outside'
194 lrwxrwxrwx 1 1000  24 Dec 15 01:39  other_dir/ln_prog_outside -> '../some_dir/prog outside'
193 -rwxr--r-- 1 1000  39 Dec 15 06:29 'other_dir/source a script'

$ cat '/tmp/test_step01/other_dir/source a script'
#!/bin/sh
. '../some_dir/prog outside'

Step 1 of 2: Obtain the correct path of the script


In the /tmp/test_step01/bin/prog in path (which is identical to /tmp/test_step01/some_dir/prog outside), these are the test codes trying to obtain the correct path of itself.

#!/bin/sh

echo -- Get its correct path ------------------------------

printf "\$0:             [%s]\n" "$0"
printf "pwd:            [%s]\n" "$(pwd)"
printf "which \$0:       [%s]\n" "$( which -- "$0" 2> /dev/null )"
printf "command -v \$0:  [%s]\n" "$( command -v -- "$0" )"
printf "BASH_SOURCE[0]: [%s]\n" "${BASH_SOURCE[0]}"

printf '\n'

The script will be called in the following different ways:

  • PATH="${PATH}:/tmp/test_step01/bin" 'prog in path'
  • PATH="${PATH}:/tmp/test_step01/bin" bash 'prog in path'. $0 loses path information.
  • '../some_dir/prog outside'. Call by relative path.
  • bash '../some_dir/prog outside'. Identical to the previous case.
  • ./ln_prog_outside runs a symlink to the program.
  • './source a script' runs a program that sources the target script.

Test calling the script in different ways.

Testing in bash:

$ ( cd /tmp/test_step01/other_dir ; PATH="${PATH}:/tmp/test_step01/bin" 'prog in path' ; PATH="${PATH}:/tmp/test_step01/bin" bash 'prog in path' ; '../some_dir/prog outside' ; bash '../some_dir/prog outside' ; ./ln_prog_outside ; './source a script' )
-- Get its correct path ------------------------------
$0:             [/tmp/test_step01/bin/prog in path]
pwd:            [/tmp/test_step01/other_dir]           #fail
which $0:       [/tmp/test_step01/bin/prog in path]
command -v $0:  [/tmp/test_step01/bin/prog in path]
BASH_SOURCE[0]: [/tmp/test_step01/bin/prog in path]

-- Get its correct path ------------------------------
$0:             [prog in path]                         #fail
pwd:            [/tmp/test_step01/other_dir]           #fail
which $0:       [/tmp/test_step01/bin/prog in path]
command -v $0:  [/tmp/test_step01/bin/prog in path]
BASH_SOURCE[0]: [/tmp/test_step01/bin/prog in path]

-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]           #fail
which $0:       [/tmp/test_step01/some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: [../some_dir/prog outside]

-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]           #fail
which $0:       [/tmp/test_step01/some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: [../some_dir/prog outside]

-- Get its correct path ------------------------------
$0:             [./ln_prog_outside]
pwd:            [/tmp/test_step01/other_dir]           #fail
which $0:       [/tmp/test_step01/other_dir/ln_prog_outside]
command -v $0:  [./ln_prog_outside]
BASH_SOURCE[0]: [./ln_prog_outside]

-- Get its correct path ------------------------------
$0:             [./source a script]                           #fail
pwd:            [/tmp/test_step01/other_dir]                  #fail
which $0:       [/tmp/test_step01/other_dir/source a script]  #fail
command -v $0:  [./source a script]                           #fail
BASH_SOURCE[0]: [../some_dir/prog outside]

In bash, ${BASH_SOURCE[0]} never fails in all cases.

Note that in some cases, ${BASH_SOURCE[0]} returns the relative path. It does not matter, as we will resolve the relative paths in step 2.

Change the hashbang from #!/bin/sh to #!/bin/zsh for files '/tmp/test_step01/some_dir/prog outside' and '/tmp/test_step01/other_dir/source a script'.

Test in zsh:

% ( cd /tmp/test_step01/other_dir ; PATH="${PATH}:/tmp/test_step01/bin" 'prog in path' ; PATH="${PATH}:/tmp/test_step01/bin" zsh -c 'prog\ in\ path' ; '../some_dir/prog outside' ; zsh '../some_dir/prog outside' ; ./ln_prog_outside ; './source a script' )
-- Get its correct path ------------------------------
$0:             [/tmp/test_step01/bin/prog in path]
pwd:            [/tmp/test_step01/other_dir]    # fail
which $0:       [/tmp/test_step01/bin/prog in path]
command -v $0:  [/tmp/test_step01/bin/prog in path]
BASH_SOURCE[0]: []                              # fail, not available

-- Get its correct path ------------------------------
$0:             [/tmp/test_step01/bin/prog in path]
pwd:            [/tmp/test_step01/other_dir]    # fail
which $0:       [/tmp/test_step01/bin/prog in path]
command -v $0:  [/tmp/test_step01/bin/prog in path]
BASH_SOURCE[0]: []                              # fail, not available

-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]    # fail
which $0:       [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []                              # fail, not available

-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]    # fail
which $0:       [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []                              # fail, not available

-- Get its correct path ------------------------------
$0:             [./ln_prog_outside]
pwd:            [/tmp/test_step01/other_dir]    # fail
which $0:       [./ln_prog_outside]
command -v $0:  [./ln_prog_outside]
BASH_SOURCE[0]: []                              # fail, not available

-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]    # fail
which $0:       [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []                              # fail, not available

In zsh, ${BASH_SOURCE[0]} is, of course, not available. Both $0 and command -v give the correct result. (Ignoring which as well.)

There are still some edge cases when the script is sourced directly from the environment rather than within a script.

In bash:

$ ( cd /tmp/test_step01/other_dir ; echo $SHELL ; . '../some_dir/prog outside' ; . ./ln_prog_outside ; . './source a script' )
/bin/bash
-- Get its correct path ------------------------------
$0:             [-bash]                        # fail
pwd:            [/tmp/test_step01/other_dir]   # fail
which $0:       []                             # fail, actually it returns 1
command -v $0:  []                             # fail, actually it returns 1
BASH_SOURCE[0]: [../some_dir/prog outside]

-- Get its correct path ------------------------------
$0:             [-bash]                        # fail
pwd:            [/tmp/test_step01/other_dir]   # fail
which $0:       []                             # fail, actually it returns 1
command -v $0:  []                             # fail, actually it returns 1
BASH_SOURCE[0]: [./ln_prog_outside]

-- Get its correct path ------------------------------
$0:             [-bash]                        # fail
pwd:            [/tmp/test_step01/other_dir]   # fail
which $0:       []                             # fail, actually it returns 1
command -v $0:  []                             # fail, actually it returns 1
BASH_SOURCE[0]: [../some_dir/prog outside]

In BASH, $BASH_SOURCE[0] still returns the correct values.

In zsh:

% ( cd /tmp/test_step01/other_dir ; echo $SHELL ; . '../some_dir/prog outside' ; . ./ln_prog_outside ; . './source a script' )
/bin/zsh
-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]   # fail
which $0:       [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []                             # fail, not available

-- Get its correct path ------------------------------
$0:             [./ln_prog_outside]
pwd:            [/tmp/test_step01/other_dir]   # fail
which $0:       [./ln_prog_outside]
command -v $0:  [./ln_prog_outside]
BASH_SOURCE[0]: []                             # fail, not available

-- Get its correct path ------------------------------
$0:             [../some_dir/prog outside]
pwd:            [/tmp/test_step01/other_dir]   # fail
which $0:       [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []                             # fail, not available

Of course ${BASH_SOURCE[0]} is not available in zsh. Do note that command -v $0 and even $0 return the correct path.

Verify our solution:

  1. If ${BASH_SOURCE[0]} is available, use it.
  2. If ${BASH_SOURCE[0]} is not available, use command -v "$0".

This "if BASH_SOURCE_0 else command-v" approach handles all the above cases well.

Important Note!


The successful of command -v "$0" depends on the value of "$0".

If you create a program with a name that is identical to an existing program, which you really should not do as it creates ambiguity, only one of the programs, either the one you newly created or the existing one, will be called.

Since both programs exist in $PATH, the one that appears earlier (on the left) in the $PATH string will be executed. command -v uses the same approach to find the path of the script file.

To illustrate this, let us consider an example where we create a file named /tmp/cryptsetup which has the same name as /sbin/cryptsetup:

$ command -v cryptsetup
/sbin/cryptsetup
$ cat /tmp/cryptsetup
#!/bin/sh
# /tmp/cryptsetup

( # script begins

echo -- 'HaHa! You are in my cryptsetup !!'
echo
printf "\$0:             [%s]\n" "$0"
printf "pwd:            [%s]\n" "$(pwd)"
printf "which \$0:       [%s]\n" "$( which -- "$0" 2> /dev/null )"
printf "command -v \$0:  [%s]\n" "$( command -v -- "$0" )"
printf "BASH_SOURCE[0]: [%s]\n" "${BASH_SOURCE[0]}"

) # script completes

Using the command PATH="${PATH}:/tmp" cryptsetup or PATH="${PATH}:/tmp" bash cryptsetup will execute the built-in /sbin/cryptsetup.

On the other hand, using the command PATH="/tmp:${PATH}" cryptsetup or PATH="/tmp:${PATH}" bash cryptsetup will execute our newly created /tmp/cryptsetup.

$ PATH="/tmp:${PATH}" cryptsetup
-- HaHa! You are in my cryptsetup !!

$0:             [/tmp/cryptsetup]
pwd:            [/tmp/test3]
which $0:       [/tmp/cryptsetup]
command -v $0:  [/tmp/cryptsetup]
BASH_SOURCE[0]: [/tmp/cryptsetup]
$ PATH="/tmp:${PATH}" bash cryptsetup
-- HaHa! You are in my cryptsetup !!

$0:             [cryptsetup]
pwd:            [/tmp/test3]
which $0:       [/tmp/cryptsetup]
command -v $0:  [/tmp/cryptsetup]
BASH_SOURCE[0]: [/tmp/cryptsetup]

In the second case above, "$0" resolves to the ambiguous name cryptsetup. However, command -v "$0" uses the same approach to search in the $PATH and returns the correct path for the intended program.

Step 2 of 2: Resolve the path.


Once we obtain a "correct" path, we proceed to canonicalize it. This could be easily done using the realpath or readlink -f commands. As we are joining the POSIX compliance club, we utilize only commands cd -P, ls -l and the $PWD variable.

This solution draws significant inspiration from the medium essay, and you can find similar code in the accepted answer of this thread.

Before moving on to the actual solution code, let us address a few issues. This will help clarify why a single cd -P may not be able to resolve the canonical path.

Setup the structure:

$ mkdir -p multi_level_dir/dir0 multi_level_file/dir{0,1,2} recursive
$ touch multi_level_dir/dir0/file0 multi_level_file/dir0/file0
$ ( cd multi_level_dir ; ln -s dir0 dir1 ; ln -s dir1 dir2 )
$ ( cd multi_level_file ; ln -s ../dir0/file0 dir1/file1 ; ln -s ../dir1/file1 dir2/file2 )
$ ln -s ../../multi_level_file/dir2/file2 multi_level_dir/dir0/lndir_lnfile
$ ( cd recursive ; ln -s file0 file1 ; ln -s file1 file2 ; ln -s file2 file0 )
$ tree
.
├── multi_level_dir
│   ├── dir0
│   │   ├── file0
│   │   └── lndir_lnfile -> ../../multi_level_file/dir2/file2
│   ├── dir1 -> dir0
│   └── dir2 -> dir1
├── multi_level_file
│   ├── dir0
│   │   └── file0
│   ├── dir1
│   │   └── file1 -> ../dir0/file0
│   └── dir2
│       └── file2 -> ../dir1/file1
└── recursive
    ├── file0 -> file2
    ├── file1 -> file0
    └── file2 -> file1

If the (script) file exists in a symlink to a directory, cd -P of dirname can return the canonical path successfully.

$ ( cd -P "$( dirname multi_level_dir/dir2/file0 )" ; printf "${PWD}\n" )
/tmp/test/multi_level_dir/dir0

However, if the directories are not symlinks but the (script) file itself is a symlink, cd -P cannot follow the symlink of the file because dirname returns its non-symlink directory. In this case, the command below will not provide the correct canonical path. The correct canonical path is /tmp/test/multi_level_file/dir0/file0.

$ ( cd -P "$( dirname multi_level_file/dir2/file2 )" ; printf "${PWD}\n" )
/tmp/test/multi_level_file/dir2

The accepted answer utilizes TARGET=$(readlink "$SOURCE") to resolve the file-symlink. I would suggest adding the -f option, so TARGET=$(readlink -f "$SOURCE") does not require additional loops. It is worth noting that readlink is not POSIX compliant. That is why the medium essay, as well as the code provided below, extract the link target from the output of ls -l.

Please note that in the medium essay, $max_symlinks is used to avoid an infinite loop in case of recursive symlinks. In the code below, I have made a change (improvement?) to check the inode number to avoid an infinite loop.

Note that readlink -f will not run into infinite loop.

$ readlink -f recursive/file2 ; echo $?
1

My proposed code to get the canonical path:

#!/bin/sh
# /home/midnite/bin/readlinkf

( # script begins

# -P : resolve symbolic link components
CDPATH='' cd -P . 2> /dev/null || exit 1

init_dir=$PWD   # remember the initial dir where this script is called.
RC=0

while [ $# -gt 0 ]; do

    # Watch out for $CDPATH gotchas
    # ref - https://bosker.wordpress.com/2012/02/12/bash-scripters-beware-of-the-cdpath/
    unset CDPATH        # to avoid changing to an unexpected directory

    # reset $PWD to the initial dir
    CDPATH='' cd -P "$init_dir" 2> /dev/null || { RC=1 ; break ; }

    target=$1

    # Use slash to distinguish dir/path or file
    #
    # Trim tailing slashes. They are meaningless.
    target=${target%"${target##*[!/]}"}
    #
    # If it is a directory, add a trailing slash.
    [ -d "${target:-/}" ] && target="${target}/"

    # inode is empty if $target does not exist.
    inode=$( stat --format '%i' "$target" 2> /dev/null )

    while true ; do
        if [ ! "$target" = "${target%/*}" ]; then   # contains '/' , target is a dir/path
            # cd -P resolves symlink(s) in the directory part
            case $target in
            ( /* )  CDPATH='' cd -P   "${target%/*}/" 2> /dev/null || { RC=1 ; break ; } ;; # abs path
            ( * )   CDPATH='' cd -P "./${target%/*}"  2> /dev/null || { RC=1 ; break ; } ;; # rel path
            esac
            target=${target##*/}    # basename
        fi

        ### Here: $PWD    is dirname
        ### Here: $target is basename only (won't be a dir)

        if [ -L "$target" ]; then   # if basename is a link
                        # extract $target from ls -l
            link=$(ls -l -- "$target" 2> /dev/null) || { RC=1 ; break ; }
            target=${link#*" $target -> "}
            [ "$(stat --format '%i' "$target" 2>&1)" = "$inode" ] && { RC=1 ; break ; }
                # if $target not found, stat error to stdout, != $inode, loop again
                # if $target = $inode, recursive encountered
        else                # basename is a file, or not exist at all
            target="${PWD%/}${target:+/}${target}"
            printf '%s\n' "${target:-/}"
            break               # success
        fi
    done

    shift
done

exit "$RC"

) # script completes

Test cases of resolvable symlinks:

$ readlinkf multi_level_dir/dir2/file0
/tmp/test/multi_level_dir/dir0/file0
$ readlinkf multi_level_file/dir2/file2
/tmp/test/multi_level_file/dir0/file0
$ readlinkf multi_level_dir/dir2/lndir_lnfile
/tmp/test/multi_level_file/dir0/file0

Test cases of recursive symlinks:

$ readlinkf recursive/file2 ; echo $?
1
$ readlink -f recursive/file2 ; echo $?
1

Test cases of dangling symlinks:

$ ln -s not_found dangling_file
$ ln -s dir/not_found dangling_dir
$ ls -lno dangling_*
lrwxrwxrwx 1 1000 13 Dec 15 02:33 dangling_dir -> dir/not_found
lrwxrwxrwx 1 1000  9 Dec 15 02:33 dangling_file -> not_found
$ readlinkf dangling_file
/tmp/test/not_found
$ readlink -f dangling_file
/tmp/test/not_found
$ readlinkf dangling_dir ; echo $?
1
$ readlink -f dangling_dir ; echo $?
1

Test cases of paths does not exist at all:

$ readlinkf no_such_file
/tmp/test/no_such_file
$ readlink -f no_such_file
/tmp/test/no_such_file
$ readlinkf no_such_dir/file ; echo $?
1
$ readlink -f no_such_dir/file ; echo $?
1

Test cases of multiple files, all successful:

$ readlinkf multi_level_dir/dir2/lndir_lnfile dangling_file ; echo $?
/tmp/test/multi_level_file/dir0/file0
/tmp/test/not_found
0
$ readlink -f multi_level_dir/dir2/lndir_lnfile dangling_file ; echo $?
/tmp/test/multi_level_file/dir0/file0
/tmp/test/not_found
0

Test cases of multiple files, at least one fails:

$ readlinkf multi_level_dir/dir2/lndir_lnfile no_such_dir/file dangling_file ; echo $?
/tmp/test/multi_level_file/dir0/file0
/tmp/test/not_found
1
$ readlink -f multi_level_dir/dir2/lndir_lnfile no_such_dir/file dangling_file ; echo $?
/tmp/test/multi_level_file/dir0/file0
/tmp/test/not_found
1

In all the above test cases, readlinkf behaves the same as readlink -f.

Final Solution


Copy the test environment:

$ cp -r /tmp/test_step{01,02}
$ ( cd /tmp/test_step02 ; ln -f 'bin/prog in path' 'some_dir/prog outside' )

Modify the file /tmp/test_step02/bin/prog in path:

#!/bin/sh

echo '======================================================'

printf "\$0:             [%s]\n" "$0"
printf "command -v \$0:  [%s]\n" "$( command -v -- "$0" )"
printf "BASH_SOURCE[0]: [%s]\n" "${BASH_SOURCE[0]}"

echo "-- if \$BASH_SOURCE[0] , else \$(command -v) -----------"

SELF_PATH=$( readlinkf "${BASH_SOURCE[0]:-"$(command -v -- "$0")"}" )

printf "SELF_PATH: [%s]\n" "$SELF_PATH"

Run the test for normal cases.

In bash:

$ ( cd /tmp/test_step02/other_dir ; PATH="${PATH}:/tmp/test_step02/bin" 'prog in path' ; PATH="${PATH}:/tmp/test_step02/bin" bash 'prog in path' ; '../some_dir/prog outside' ; bash '../some_dir/prog outside' ; ./ln_prog_outside ; './source a script' )
======================================================
$0:             [/tmp/test_step02/bin/prog in path]
command -v $0:  [/tmp/test_step02/bin/prog in path]
BASH_SOURCE[0]: [/tmp/test_step02/bin/prog in path]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/bin/prog in path]
======================================================
$0:             [prog in path]
command -v $0:  [/tmp/test_step02/bin/prog in path]
BASH_SOURCE[0]: [/tmp/test_step02/bin/prog in path]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/bin/prog in path]
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: [../some_dir/prog outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: [../some_dir/prog outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [./ln_prog_outside]
command -v $0:  [./ln_prog_outside]
BASH_SOURCE[0]: [./ln_prog_outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [./source a script]
command -v $0:  [./source a script]
BASH_SOURCE[0]: [../some_dir/prog outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]

Modidy the hashbangs. Test again in zsh:

% ( cd /tmp/test_step02/other_dir ; PATH="${PATH}:/tmp/test_step02/bin" 'prog in path' ; PATH="${PATH}:/tmp/test_step02/bin" zsh -c 'prog\ in\ path' ; '../some_dir/prog outside' ; zsh '../some_dir/prog outside' ; ./ln_prog_outside ; './source a script' )
======================================================
$0:             [/tmp/test_step02/bin/prog in path]
command -v $0:  [/tmp/test_step02/bin/prog in path]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/bin/prog in path]
======================================================
$0:             [/tmp/test_step02/bin/prog in path]
command -v $0:  [/tmp/test_step02/bin/prog in path]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/bin/prog in path]
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [./ln_prog_outside]
command -v $0:  [./ln_prog_outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]

Run the test for edge cases where the script is sourced directly from the environment:

In bash:

$ ( cd /tmp/test_step02/other_dir ; echo $SHELL ; . '../some_dir/prog outside' ; . ./ln_prog_outside ; . './source a script' )
/bin/bash
======================================================
$0:             [-bash]
command -v $0:  []
BASH_SOURCE[0]: [../some_dir/prog outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [-bash]
command -v $0:  []
BASH_SOURCE[0]: [./ln_prog_outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [-bash]
command -v $0:  []
BASH_SOURCE[0]: [../some_dir/prog outside]
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]

In zsh:

% ( cd /tmp/test_step02/other_dir ; echo $SHELL ; . '../some_dir/prog outside' ; . ./ln_prog_outside ; . './source a script' )
/bin/zsh
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [./ln_prog_outside]
command -v $0:  [./ln_prog_outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]
======================================================
$0:             [../some_dir/prog outside]
command -v $0:  [../some_dir/prog outside]
BASH_SOURCE[0]: []
-- if $BASH_SOURCE[0] , else $(command -v) -----------
SELF_PATH: [/tmp/test_step02/some_dir/prog outside]

All the test cases above produce the correct results.

Solution: Explicit Recap


This one-liner, which is POSIX compliant, returns the canonical file path of the script itself:

SELF_PATH=$( readlinkf "${BASH_SOURCE[0]:-"$(command -v -- "$0")"}" )

given that the program readlinkf is implemented in the system. (Please refer to the code above. Search for /home/midnite/bin/readlinkf.)

If it is certain that the target system supports readlink -f, which is not a POSIX requirement,(3) it is safe to change readlinkf to readlink -f, and the one-liner above will produce the same correct results.

Remarks

  1. Credit to https://hynek.me/til/which-not-posix/
  2. What's the difference between "realpath" and "readlink -f", first answer, https://unix.stackexchange.com/a/136527/150246
  3. https://medium.com/mkdir-awesome/posix-alternatives-for-readlink-21a4bfe0455c
Superannuate answered 12/9, 2008 at 20:40 Comment(3)
You must remember this one-liner and include this line in any script where you want to apply it. Wrap ${BASH_SOURCE[0]} in a function named get_self_path and place this function in a script file called get_self_path.sh. In the main script, you source . get_self_path.sh and then call the function get_self_path. However, this approach does not work as expected; it returns the path of get_self_path.sh instead. The silver lining is that you can wrap ${BASH_SOURCE[0]} in a function defined within the same script file. This allows you to reuse the code within that script file.Superannuate
answer is good but too long...Structure
@Structure - Thank you. I think it is too long too. I should have made it more concise.Superannuate
O
3

The following will return the current directory of the script

  • works if it's sourced, or not sourced
  • works if run in the current directory, or some other directory.
  • works if relative directories are used.
  • works with bash, not sure of other shells.
/tmp/a/b/c $ . ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ ./test.sh
/tmp/a/b/c

/tmp/a/b/c $ /tmp/a/b/c/test.sh
/tmp/a/b/c

/tmp/a/b/c $ cd

~ $ . /tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ . ../../tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ /tmp/a/b/c/test.sh
/tmp/a/b/c

~ $ ../../tmp/a/b/c/test.sh
/tmp/a/b/c

test.sh

#!/usr/bin/env bash

# snagged from: https://mcmap.net/q/19845/-how-to-retrieve-absolute-path-given-relative
function toAbsPath {
    local target
    target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

function getScriptDir(){
  local SOURCED
  local RESULT
  (return 0 2>/dev/null) && SOURCED=1 || SOURCED=0

  if [ "$SOURCED" == "1" ]
  then
    RESULT=$(dirname "$1")
  else
    RESULT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
  fi
  toAbsPath "$RESULT"
}

SCRIPT_DIR=$(getScriptDir "$0")
echo "$SCRIPT_DIR"
Oman answered 12/9, 2008 at 20:40 Comment(0)
F
3

This solution applies only to Bash. Note that the commonly supplied answer ${BASH_SOURCE[0]} won't work if you try to find the path from within a function.

I've found this line to always work, regardless of whether the file is being sourced or run as a script.

dirname ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

If you want to follow symlinks use readlink on the path you get above, recursively or non-recursively.

Here's a script to try it out and compare it to other proposed solutions. Invoke it as source test1/test2/test_script.sh or bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

The reason the one-liner works is explained by the use of the BASH_SOURCE environment variable and its associated FUNCNAME.

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

FUNCNAME

An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME doesn't have any effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.

[Source: Bash manual]

Fiann answered 12/9, 2008 at 20:40 Comment(0)
S
2

If not sourced by parent script and not symlinked, $0 is enough:

script_path="$0"

If sourced by parent script and not symlinked, use $BASH_SOURCE or ${BASH_SOURCE[0]}:

script_path="$BASH_SOURCE"

If symlinked, use $BASH_SOURCE with realpath or readlink -f to get the real file path:

script_path="$(realpath "$BASH_SOURCE")"

In addition, realpath or readlink -f returns the absolute path.

To get the directory of the script, use dirname:

script_directory="$(dirname "$script_path")"

Note

Sestina answered 12/9, 2008 at 20:40 Comment(0)
D
2

I tried the followings with 3 different executions.

echo $(realpath $_)

. application         # /correct/path/to/dir or /path/to/temporary_dir
bash application      # /path/to/bash
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $(dirname $0))

. application         # failed with `realpath: missing operand`
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

echo $(realpath $BASH_SOURCE)

$BASH_SOURCE is basically the same with ${BASH_SOURCE[0]}.

. application         # /correct/path/to/dir
bash application      # /correct/path/to/dir
/PATH/TO/application  # /correct/path/to/dir

Only $(realpath $BASH_SOURCE) seems to be reliable.

Degree answered 12/9, 2008 at 20:40 Comment(0)
B
2

Python was mentioned a few times. Here is the JavaScript (i.e., Node.js) alternative:

baseDirRelative=$(dirname "$0")
baseDir=$(node -e "console.log(require('path').resolve('$baseDirRelative'))") # Get absolute path using Node.js

echo $baseDir
Bromide answered 12/9, 2008 at 20:40 Comment(0)
R
2

If your Bash script is a symlink, then this is the way to do it:

#!/usr/bin/env bash

dirn="$(dirname "$0")"
rl="$(readlink "$0")";
exec_dir="$(dirname $(dirname "$rl"))";
my_path="$dirn/$exec_dir";
X="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})"

X is the directory that contains your Bash script (the original file, not the symlink). I swear to God this works, and it is the only way I know of doing this properly.

Rake answered 12/9, 2008 at 20:40 Comment(0)
A
2

This is how I work it on my scripts:

pathvar="$( cd "$( dirname $0 )" && pwd )"

This will tell you which directory the Launcher (current script) is being executed from.

Ablation answered 12/9, 2008 at 20:40 Comment(0)
W
0

You can get the source directory of a Bash script from within the script itself on follow short way:

script_path=$(dirname "$(readlink -f "$0")")"/"
echo "$script_path"

Sample output:

/home/username/desktop/
Wreckfish answered 12/9, 2008 at 20:40 Comment(0)
H
0

I think the simplest answer is a parameter expansion of the original variable:

#!/usr/bin/env bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "opt1; original answer: $DIR"
echo ''

echo "opt2; simple answer  : ${BASH_SOURCE[0]%/*}"

It should produce output like:

$ /var/tmp/test.sh
opt1; original answer: /var/tmp

opt2; simple answer  : /var/tmp

The variable/parameter expansion ${BASH_SOURCE[0]%/*}" seems much easier to maintain.

Heliochrome answered 12/9, 2008 at 20:40 Comment(0)
R
0

The below stores the script's directory path in the dir variable.

(It also tries to support being executed under Cygwin in Windows.)

And at last it runs the my-sample-app executable with all arguments passed to this script using "$@":

#!/usr/bin/env sh

dir=$(cd "${0%[/\\]*}" > /dev/null && pwd)

if [ -d /proc/cygdrive ]; then
    case "$(uname -s)" in
        CYGWIN*|MINGW32*|MSYS*|MINGW*)
            # We are under Windows, so translate path to Windows format.
            dir=$(cygpath -m "$dir");
            ;;
    esac
fi

# Runs the executable which is beside this script
"${dir}/my-sample-app" "$@"
Rumal answered 12/9, 2008 at 20:40 Comment(2)
What is "Windows PHP"? "PHP For Windows"? Or WAMP? Or something else?Barela
With help of another post removed the need for PHP (and now checks if is running under Windows).Rumal
K
0

Here's an excerpt from my answer to shell script: check directory name and convert to lowercase in which I demonstrate not only how to solve this problem with very basic POSIX-specified utilities, I also address how to very simply store the function's results in a returned variable...

...Well, as you can see, with some help, I hit upon a pretty simple and very powerful solution:

I can pass the function a sort of messenger variable and dereference any explicit use of the resulting function's argument's $1 name with eval as necessary, and, upon the function routine's completion, I use eval and a backslashed quoting trick to assign my messenger variable the value I desire without ever having to know its name.

In full disclosure, ... (I found the messenger variable portion of this) and at Rich's sh tricks and I have also excerpted the relevant portion of his page below my own answer's excerpt.

... EXCERPT: ...

Though not strictly POSIX yet, realpath is a GNU core application since 2012. Full disclosure: never heard of it before I noticed it in the info coreutils TOC and immediately thought of [the linked] question, but using the following function as demonstrated should reliably, (soon POSIXLY?), and, I hope, efficiently provide its caller with an absolutely sourced $0:

% _abs_0() {
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}";
> }
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh

It may be worth highlighting that this solution uses POSIX parameter expansion to first check if the path actually needs expanding and resolving at all before attempting to do so. This should return an absolutely sourced $0via a messenger variable (with the notable exception that it will preserve symlinks) as efficiently as I could imagine it could be done whether or not the path is already absolute. ...

(minor edit: before finding realpath in the docs, I had at least pared down my version of (the version below) not to depend on the time field (as it does in the first ps command), but, fair warning, after testing some I'm less convinced ps is fully reliable in its command path expansion capacity)

On the other hand, you could do this:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"

eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

... And from Rich's sh tricks: ...

Returning strings from a shell function

As can be seen from the above pitfall of command substitution, standard output is not a good avenue for shell functions to return strings to their caller, unless the output is in a format where trailing newlines are insignificant. Certainly such practice is not acceptable for functions meant to deal with arbitrary strings. So, what can be done?

Try this:

func () {
body here
eval "$1=\${foo}"
}

Of course, ${foo} could be replaced by any sort of substitution. The key trick here is the eval line and the use of escaping. The “$1” is expanded when the argument to eval is constructed by the main command parser. But the “${foo}” is not expanded at this stage, because the “$” has been quoted. Instead, it’s expanded when eval evaluates its argument. If it’s not clear why this is important, consider how the following would be bad:

foo='hello ; rm -rf /'
dest=bar
eval "$dest=$foo"

But of course the following version is perfectly safe:

foo='hello ; rm -rf /'
dest=bar
eval "$dest=\$foo"

Note that in the original example, “$1” was used to allow the caller to pass the destination variable name as an argument the function. If your function needs to use the shift command, for instance to handle the remaining arguments as “$@”, then it may be useful to save the value of “$1” in a temporary variable at the beginning of the function.

Knickknack answered 12/9, 2008 at 20:40 Comment(0)
W
0

Here is a pure Bash solution

$ cat a.sh
BASENAME=${BASH_SOURCE/*\/}
DIRNAME=${BASH_SOURCE%$BASENAME}.
echo $DIRNAME

$ a.sh
/usr/local/bin/.

$ ./a.sh
./.

$ . a.sh
/usr/local/bin/.

$ /usr/local/bin/a.sh
/usr/local/bin/.
Wirra answered 12/9, 2008 at 20:40 Comment(0)
C
0

I usually do:

LIBDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")")
source $LIBDIR/lib.sh
Cantilever answered 12/9, 2008 at 20:40 Comment(0)
G
-1

Look at the test at bottom with weird directory names.

To change the working directory to the one where the Bash script is located, you should try this simple, tested and verified with shellcheck solution:

#!/bin/bash --
cd "$(dirname "${0}")"/. || exit 2

The test:

$ ls 
application
$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ mv application *testdir
$ ln -s *testdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47symlink" "")"
$ ls -lb
total 4
lrwxrwxrwx 1 jay stacko   46 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'symlink -> \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
drwxr-xr-x 2 jay stacko 4096 Mar 30 20:44 \001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ *testdir/application && printf "SUCCESS\n" ""
SUCCESS
$ *symlink/application && printf "SUCCESS\n" ""
SUCCESS
Gaudy answered 12/9, 2008 at 20:40 Comment(0)
E
-1

The key part is that I am reducing the scope of the problem: I forbid indirect execution of the script via the path (as in /bin/sh [script path relative to path component]).

This can be detected because $0 will be a relative path which does not resolve to any file relative to the current folder. I believe that direct execution using the #! mechanism always results in an absolute $0, including when the script is found on the path.

I also require that the pathname and any pathnames along a chain of symbolic links only contain a reasonable subset of characters, notably not \n, >, * or ?. This is required for the parsing logic.

There are a few more implicit expectations which I will not go into (look at this answer), and I do not attempt to handle deliberate sabotage of $0 (so consider any security implications). I expect this to work on almost any Unix-like system with a Bourne-like /bin/sh.

#!/bin/sh
(
    path="${0}"
    while test -n "${path}"; do
        # Make sure we have at least one slash and no leading dash.
        expr "${path}" : / > /dev/null || path="./${path}"
        # Filter out bad characters in the path name.
        expr "${path}" : ".*[*?<>\\]" > /dev/null && exit 1
        # Catch embedded new-lines and non-existing (or path-relative) files.
        # $0 should always be absolute when scripts are invoked through "#!".
        test "`ls -l -d "${path}" 2> /dev/null | wc -l`" -eq 1 || exit 1
        # Change to the folder containing the file to resolve relative links.
        folder=`expr "${path}" : "\(.*/\)[^/][^/]*/*$"` || exit 1
        path=`expr "x\`ls -l -d "${path}"\`" : "[^>]* -> \(.*\)"`
        cd "${folder}"
        # If the last path was not a link then we are in the target folder.
        test -n "${path}" || pwd
    done
)
Exhortation answered 12/9, 2008 at 20:40 Comment(0)
D
-1

No forks (besides subshell) and can handle "alien" pathname forms like those with newlines as some would claim:

IFS= read -rd '' DIR < <([[ $BASH_SOURCE != */* ]] || cd "${BASH_SOURCE%/*}/" >&- && echo -n "$PWD")
Delorsedelos answered 12/9, 2008 at 20:40 Comment(0)
A
-1
cur_dir=`old=\`pwd\`; cd \`dirname $0\`; echo \`pwd\`; cd $old;`
Alfi answered 12/9, 2008 at 20:40 Comment(0)
I
-1

Try something like this:

function get_realpath() {

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

# Reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # Success

}

function get_dirname(){

local realpath="$(get_realpath "$1")"
if (( $? )) # True when non-zero.
then
    return $? # Failure
fi
echo "${realpath%/*}"
return 0 # Success

}

# Then from the top level:
get_dirname './script.sh'

# Or within a script:
get_dirname "$0"

# Can even test the outcome!
if (( $? )) # True when non-zero.
then
    exit 1 # Failure
fi

These functions and related tools are part of our product that has been made available to the community for free and can be found at GitHub as realpath-lib. It's simple, clean and well documented (great for learning), pure Bash and has no dependencies. Good for cross-platform use too. So for the above example, within a script you could simply:

source '/path/to/realpath-lib'

get_dirname "$0"

if (( $? )) # True when non-zero.
then
    exit 1 # Failure
fi
Inertia answered 12/9, 2008 at 20:40 Comment(0)
S
-1

I usually include the following at the top of my scripts which works in the majority of cases:

[ "$(dirname $0)" = '.' ] && SOURCE_DIR=$(pwd) || SOURCE_DIR=$(dirname $0);
ls -l $0 | grep -q ^l && SOURCE_DIR=$(ls -l $0 | awk '{print $NF}');

The first line assigns source based on the value of pwd if run from the current path or dirname if called from elsewhere.

The second line examines the path to see if it is a symlink and if so, updates SOURCE_DIR to the location of the link itself.

There are probably better solutions out there, but this is the cleanest I've managed to come up with myself.

Sabra answered 12/9, 2008 at 20:40 Comment(0)
C
-1
function getScriptAbsoluteDir { # fold>>
    # @description used to get the script path
    # @param $1 the script $0 parameter
    local script_invoke_path="$1"
    local cwd=`pwd`

    # absolute path ? if so, the first character is a /
    if test "x${script_invoke_path:0:1}" = 'x/'
    then
        RESULT=`dirname "$script_invoke_path"`
    else
        RESULT=`dirname "$cwd/$script_invoke_path"`
    fi
} # <<fold
Consecration answered 12/9, 2008 at 20:40 Comment(3)
sorry I'm a bit of a bash scrip noob, do I call this function by just typing getScriptAbsoluteDir or local currdir='getScriptAbsoluteDir'?Enchondroma
-1: The function keyword isn't available on POSIX shells, and is a needlessly incompatible bash/ksh/&c. extension. Also, if you're on a shell new enough to have that extension, you don't need the test "x[...]" hack.Cyrillic
This almost looks like Python...I can't seem to remember that I've ever used the "local" keyword in a shell script...And to what Charles said, use if [[ "${script_invoke_path:0:1}" == "/" ]]; then and so on. Note the logical double == "equals" operator.Quadragesima
A
-2

Based on this answer, I suggest the clarified version that gets SCRIPT_HOME as the containing folder of any currently-running Bash script:

s=${BASH_SOURCE[0]} ; s=`dirname $s` ; SCRIPT_HOME=`cd $s ; pwd`
echo $SCRIPT_HOME
Alita answered 12/9, 2008 at 20:40 Comment(0)
F
-3

I want to comment on the previous answer up there (How can I get the source directory of a Bash script from within the script itself?), but don't have enough reputation to do that.

I found a solution for this two years ago on Apple's documentation site: https://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/AdvancedTechniques/AdvancedTechniques.html. And I stuck to this method afterwards. It cannot handle soft link, but otherwise works pretty well for me. I'm posting it here for any who needs it and as a request for comment.

#!/bin/sh

# Get an absolute path for the poem.txt file.
POEM="$PWD/../poem.txt"

# Get an absolute path for the script file.
SCRIPT="$(which $0)"
if [ "x$(echo $SCRIPT | grep '^\/')" = "x" ] ; then
    SCRIPT="$PWD/$SCRIPT"
fi

As shown by the code, after you get the absolute path of the script, then you can use the dirname command to get the path of the directory.

Fernando answered 12/9, 2008 at 20:40 Comment(0)
A
-3

This one-liner works on Cygwin even if the script has been called from Windows with bash -c <script>:

set mydir="$(cygpath "$(dirname "$0")")"
Aerie answered 12/9, 2008 at 20:40 Comment(0)
M
-3

I want to make sure that the script is running in its directory. So

cd $(dirname $(which $0) )

After this, if you really want to know where the you are running then run the command below.

DIR=$(/usr/bin/pwd)
Mcelhaney answered 12/9, 2008 at 20:40 Comment(1)
How does this differ from other answers?Artima
H
-4

This is what I crafted throughout the years to use as a header on my Bash scripts:

## BASE BRAIN - Get where you're from and who you are.
MYPID=$$
ORIGINAL_DIR="$(pwd)" # This is not a hot air balloon ride..
fa="$0" # First Assumption
ta= # Temporary Assumption
wa= # Weighed Assumption
while true; do
    [ "${fa:0:1}" = "/" ] && wa=$0 && break
    [ "${fa:0:2}" = "./" ] && ta="${ORIGINAL_DIR}/${fa:2}" && [ -e "$ta" ] && wa="$ta" && break
    ta="${ORIGINAL_DIR}/${fa}" && [ -e "$ta" ] && wa="$ta" && break
done
SW="$wa"
SWDIR="$(dirname "$wa")"
SWBIN="$(basename "$wa")"
unset ta fa wa
( [ ! -e "$SWDIR/$SWBIN" ] || [ -z "$SW" ] ) && echo "I could not find my way around :( possible bug in the TOP script" && exit 1

At this point, your variables SW, SWDIR, and SWBIN contain what you need.

Hazem answered 12/9, 2008 at 20:40 Comment(1)
A good candidate for a code obscurity contest. Instead of ORIGINAL_DIR you can use PWD, and what does MYPID has to do with the answer anyway?Hotshot
V
-4

There is no 100% portable and reliable way to request a path to a current script directory. Especially between different backends like Cygwin, MinGW, MSYS, Linux, etc. This issue was not properly and completely resolved in Bash for ages.

For example, this could not be resolved if you want to request the path after the source command to make nested inclusion of another Bash script which is in turn use the same source command to include another Bash script and so on.

In case of the source command, I suggest to replace the source command with something like this:

function include()
{
  if [[ -n "$CURRENT_SCRIPT_DIR" ]]; then
    local dir_path=... get directory from `CURRENT_SCRIPT_DIR/$1`, depends if $1 is absolute path or relative ...
    local include_file_path=...
  else
    local dir_path=... request the directory from the "$1" argument using one of answered here methods...
    local include_file_path=...
  fi
  ... push $CURRENT_SCRIPT_DIR in to stack ...
  export CURRENT_SCRIPT_DIR=... export current script directory using $dir_path ...
  source "$include_file_path"
  ... pop $CURRENT_SCRIPT_DIR from stack ...
}

From now on, the use of include(...) is based on previous CURRENT_SCRIPT_DIR in your script.

This only works when you can replace all source commands by include command. If you can't, then you have no choice. At least until developers of the Bash interpreter make an explicit command to request the current running script directory path.

My own closest implementation to this:
https://github.com/andry81/tacklelib/tree/HEAD/bash/tacklelib/bash_tacklelib

(search for the tkl_include function)

Velma answered 12/9, 2008 at 20:40 Comment(0)
G
-6

The chosen answer works very well. I'm posting my solution for anyone looking for shorter alternatives that still addresses sourcing, executing, full paths, relative paths, and symlinks. Finally, this will work on macOS, given that it cannot be assumed that GNU's coreutils' version of readlink is available.

The gotcha is that it's not using Bash, but it is easy to use in a Bash script. While the OP did not place any constraints on the language of the solution, it's probably best that most have stayed within the Bash world. This is just an alternative, and possibly an unpopular one.

PHP is available on macOS by default, and installed on a number of other platforms, though not necessarily by default. I realize this is a shortcoming, but I'll leave this here for any people coming from search engines, anyway.

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
Guinness answered 12/9, 2008 at 20:40 Comment(1)
There was nothing about MacOS in the question. I think this answer should start with "Another approach is to use PHP instead of relying on BASH". Because this reveals only at the end of the answer.Dietrich
E
-8

Keep it simple.

#!/usr/bin/env bash
sourceDir=`pwd`
echo $sourceDir
Exieexigency answered 12/9, 2008 at 20:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.