This question already has an answer here:
I have a Bash script that needs to know its full path. I'm trying to find a broadly-compatible way of doing that without ending up with relative or funky-looking paths. I only need to support Bash, not sh, csh, etc.
What I've found so far:
The accepted answer to Getting the source directory of a Bash script from within addresses getting the path of the script via
dirname $0
, which is fine, but that may return a relative path (like.
), which is a problem if you want to change directories in the script and have the path still point to the script's directory. Still,dirname
will be part of the puzzle.The accepted answer to Bash script absolute path with OS X (OS X specific, but the answer works regardless) gives a function that will test to see if
$0
looks relative and if so will pre-pend$PWD
to it. But the result can still have relative bits in it (although overall it's absolute) — for instance, if the script ist
in the directory/usr/bin
and you're in/usr
and you typebin/../bin/t
to run it (yes, that's convoluted), you end up with/usr/bin/../bin
as the script's directory path. Which works, but...The
readlink
solution on this page, which looks like this:# Absolute path to this script. /home/user/bin/foo.sh SCRIPT=$(readlink -f $0) # Absolute path this script is in. /home/user/bin SCRIPTPATH=`dirname $SCRIPT`
But
readlink
isn't POSIX and apparently the solution relies on GNU'sreadlink
where BSD's won't work for some reason (I don't have access to a BSD-like system to check).
So, various ways of doing it, but they all have their caveats.
What would be a better way? Where "better" means:
- Gives me the absolute path.
- Takes out funky bits even when invoked in a convoluted way (see comment on #2 above). (E.g., at least moderately canonicalizes the path.)
- Relies only on Bash-isms or things that are almost certain to be on most popular flavors of *nix systems (GNU/Linux, BSD and BSD-like systems like OS X, etc.).
- Avoids calling external programs if possible (e.g., prefers Bash built-ins).
- (Updated, thanks for the heads up, wich) It doesn't have to resolve symlinks (in fact, I'd kind of prefer it left them alone, but that's not a requirement).
Just for the hell of it I've done a bit of hacking on a script that does things purely textually, purely in Bash. I hope I caught all the edge cases.
Note that the
${var//pat/repl}
that I mentioned in the other answer doesn't work since you can't make it replace only the shortest possible match, which is a problem for replacing/foo/../
as e.g./*/../
will take everything before it, not just a single entry. And since these patterns aren't really regexes I don't see how that can be made to work. So here's the nicely convoluted solution I came up with, enjoy. ;)By the way, let me know if you find any unhandled edge cases.
We have placed our own product realpath-lib on GitHub for free and unencumbered community use.
Shameless plug but with this Bash library you can:
This function is the core of the library:
It doesn't require any external dependencies, just Bash 4+. Also contains functions to
get_dirname
,get_filename
,get_stemname
andvalidate_pathvalidate_realpath
. It's free, clean, simple and well documented, so it can be used for learning purposes too, and no doubt can be improved. Try it across platforms.Update: After some review and testing we have replaced the above function with something that achieves the same result (without using dirname, only pure Bash) but with better efficiency:
This also includes an environment setting
no_symlinks
that provides the ability to resolve symlinks to the physical system. By default it keeps symlinks intact.Bourne shell (
sh
) compliant way:Here's what I've come up with (edit: plus some tweaks provided by sfstewman, levigroker, Kyle Strand, and Rob Kennedy), that seems to mostly fit my "better" criteria:
That
SCRIPTPATH
line seems particularly roundabout, but we need it rather thanSCRIPTPATH=`pwd`
in order to properly handle spaces and symlinks.Note also that esoteric situations, such as executing a script that isn't coming from a file in an accessible file system at all (which is perfectly possible), is not catered to there (or in any of the other answers I've seen).
As realpath is not installed per default on my Linux system, the following works for me:
$SCRIPT
will contain the real file path to the script and$SCRIPTPATH
the real path of the directory containing the script.Before using this read the comments of this answer.
One liner