How to resolve symbolic links in a shell script
Solution 1
According to the standards, pwd -P
should return the path with symlinks resolved.
C function char *getcwd(char *buf, size_t size)
from unistd.h
should have the same behaviour.
Solution 2
readlink -f "$path"
Editor's note: The above works with GNU readlink
and FreeBSD/PC-BSD/OpenBSD readlink
, but not on OS X as of 10.11.
GNU readlink
offers additional, related options, such as -m
for resolving a symlink whether or not the ultimate target exists.
Note since GNU coreutils 8.15 (2012-01-06), there is a realpath program available that is less obtuse and more flexible than the above. It's also compatible with the FreeBSD util of the same name. It also includes functionality to generate a relative path between two files.
realpath $path
[Admin addition below from comment by halloleo —danorton]
For Mac OS X (through at least 10.11.x), use readlink
without the -f
option:
readlink $path
Editor's note: This will not resolve symlinks recursively and thus won't report the ultimate target; e.g., given symlink a
that points to b
, which in turn points to c
, this will only report b
(and won't ensure that it is output as an absolute path).
Use the following perl
command on OS X to fill the gap of the missing readlink -f
functionality:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"
Solution 3
"pwd -P" seems to work if you just want the directory, but if for some reason you want the name of the actual executable I don't think that helps. Here's my solution:
#!/bin/bash
# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")
# resolve symlinks
while [[ -h $SELF_PATH ]]; do
# 1) cd to directory of the symlink
# 2) cd to the directory of where the symlink points
# 3) get the pwd
# 4) append the basename
DIR=$(dirname -- "$SELF_PATH")
SYM=$(readlink "$SELF_PATH")
SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done
Solution 4
One of my favorites is realpath foo
realpath - return the canonicalized absolute pathname realpath expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path. The resulting path will have no symbolic link, '/./' or '/../' components.
Solution 5
readlink -e [filepath]
seems to be exactly what you're asking for - it accepts an arbirary path, resolves all symlinks, and returns the "real" path - and it's "standard *nix" that likely all systems already have
Related videos on Youtube
Comments
-
Greg Hewgill about 4 years
Given an absolute or relative path (in a Unix-like system), I would like to determine the full path of the target after resolving any intermediate symlinks. Bonus points for also resolving ~username notation at the same time.
If the target is a directory, it might be possible to chdir() into the directory and then call getcwd(), but I really want to do this from a shell script rather than writing a C helper. Unfortunately, shells have a tendency to try to hide the existence of symlinks from the user (this is bash on OS X):
$ ls -ld foo bar drwxr-xr-x 2 greg greg 68 Aug 11 22:36 bar lrwxr-xr-x 1 greg greg 3 Aug 11 22:36 foo -> bar $ cd foo $ pwd /Users/greg/tmp/foo $
What I want is a function resolve() such that when executed from the tmp directory in the above example, resolve("foo") == "/Users/greg/tmp/bar".
-
Phil Ross about 15 yearsOn Debian (etch and later) this command is available in the realpath package.
-
Bkkbrad almost 15 yearsThis doesn't work in Mac OS X - see stackoverflow.com/questions/1055671/…
-
dala about 14 yearsThis just works for (the current) directory? If the target is a file, it will not give anything...
-
Tom Howard over 12 yearsDoesn't work if the link is broken as you cannot make it your current path
-
jkp over 12 yearsshame about OS X incompatibility, otherwise nice +1
-
pixelbeat about 12 yearsrealpath is now (Jan 2012) part of coreutils and backwards compatible with the debian and BSD variant
-
Kief over 11 yearsOn OS X you can install coreutils with homebrew. It installs it as "grealpath".
-
halloleo over 11 years
readlink
works on OSX, but needs another syntax:readlink $path
without the-f
. -
Magnus almost 11 yearsreadlink fails to de-reference multiple layers of symlink though, it just derefs one layer at a time
-
pixelbeat almost 11 yearsBoth
readlink -f
andrealpath
in coreutils, do dereference all "layers" of symlinks -
blubberdiblub over 10 yearsEven the most simple shells support subshells, and cd'ing in those subshells doesn't affect the outside. So if your link to resolve is a directory, you can do something along the lines of
( CDPATH="" cd -P -- "$path" ; pwd -P )
-
zdd over 10 yearsare these two program built-in of Sun Solaris?, I can't find them on my host. my host info is: SunOS xxx 5.10 Generic_138888-08 sun4v sparc SUNW,SPARC-Enterprise-T5120
-
pixelbeat over 10 yearsNo solaris doesn't have these. You would need to build GNU coreutils for your system.
-
toxalot over 10 yearsI don't have
realpath
on Centos 6 with GNU coreutils 8.4.31. I've come across several others on Unix & Linux that have a GNU coreutils packaged withoutrealpath
. So it seems to be dependent on more than just version. -
toxalot over 10 yearsI don't have
realpath
on Centos 6 with GNU coreutils 8.4.31. I've come across several others on Unix & Linux that have a GNU coreutils packaged withoutrealpath
. So it seems to be dependent on more than just version. -
pixelbeat over 10 yearsNote by design the coreutils realpath is mostly compatible with the separate realpath package/program on debian/ubuntu. The separate realpath maintainer would like to switch to the GNU coreutils version too. This is tracked at bugs.debian.org/730779
-
Joe Heyming over 9 yearsHey hey, I have one caveat. If you use ls -F to read your symlink into a variable, you will get an @ in your symlink name. This causes readlink to fail silently. It caused much confusion for me.
-
Jonathan Baldwin about 9 yearsPython 2.6+ is available on more end-user systems than php, so
python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"
would probably make more sense. Still, by the time you need to use Python from a shell script, you should probably just use Python and save yourself the headache of cross platform shell scripting. -
Dave over 8 yearsMy earlier answer does exactly the same thing in about 1/4 of the space :) It's pretty basic shell scripting and not worthy of a git repo.
-
Dave over 8 yearsYou don't need any more than the sh built-ins readlink, dirname and basename.
-
mklement0 over 8 years@Dave:
dirname
,basename
, andreadlink
are external utilities, not shell built-ins;dirname
andbasename
are part of POSIX,readlink
is not. -
mklement0 over 8 yearsTo summarize with the benefit of hindsight: This answer works only in very limited circumstances, namely if the symlink of interest is to a directory that actually exists; plus, you must
cd
to it first, before callingpwd -P
. In other words: it won't allow you to resolve (see the target of) symlinks to files or of broken symlinks, and for resolving existing-directory symlinks you have to do additional work (restore the previous working dir or localize thecd
andpwd -P
calls in a subshell). -
mklement0 over 8 yearsThis will break with (a) any path containing whitespace or shell metacharacters and (b) broken symlinks (not an issue with if you just want the running script's parent path, assuming (a) doesn't apply).
-
Dave over 8 years@mklement0 - You are quite right. They are provided by CoreUtils or equivalent. I shouldn't visit SO after 1am. The essence of my comment is that neither PHP nor any other language interpreter beyond that installed in a base system are required. I have used the script I provided on this page on every linux variant since 1997 and MacOS X since 2006 without error. The OP didn't ask for a POSIX solution. Their specific environment was Mac OS X.
-
Dave over 8 yearsIf you are concerned with whitespace use quotes.
-
mklement0 over 8 yearsPlease do - although that won't address (b).
-
Dave over 8 yearsPlease show me an example of b) where this fails. By definition, a broken symlink points to a directory entry that doesn't exist. The point of this script is to resolve symlinks in the other direction. If the symlink were broken you wouldn't be executing the script. This example is intended to demonstrate resolving the currently executing script.
-
mklement0 over 8 years"intended to demonstrate resolving the currently executing script" - indeed, which is a narrowing of the question's scope you've elected to focus on; that is perfectly fine, as long as you say so. Since you didn't, I stated it in my comment. Please fix the quoting issue, which is a problem irrespective of the scope of the answer.
-
mklement0 over 8 years@Dave: Yes, it is possible to do it with stock utilities, but it is also hard to do so (as evidenced by the shortcomings of your script). If OS X were truly the focus, then this answer is perfectly fine - and much simpler - given that
php
comes with OS X. However, even though the body of the question mentions OS X, it is not tagged as such, and it's become clear that people on various platforms come here for answers, so it's worth pointing out what's platform-specific / non-POSIX. -
jarno over 8 yearsIn my experience, quoting bypasses aliases, not shell functions, unlike you first tell.
-
jarno over 8 yearsI tested that I can define
[
as an alias indash
andbash
and as a shell function inbash
. -
jarno over 8 yearsI made my point as a separate question.
-
mklement0 over 8 years@jarno: Good points; I've updated my answer; let me know if you think there's still a problem.
-
jarno over 8 yearsJust a note: I could not define
while
as shell function in eitherbash
ordash
. I can definewhile
as an alias inbash
; thereafter it is recognized as alias (unlike indash
). (Tested by GNU bash, version 4.3.11(1)-release, and dash package version 0.5.7) -
mklement0 over 8 years@jarno: You can define
while
as a function inbash
,ksh
, andzsh
(but notdash
), but only withfunction <name>
syntax:function while { echo foo; }
works (while() { echo foo; }
doesn't). However, this wont' shadow thewhile
keyword, because keywords have higher precedence than functions (the only way to invoke this function is as\while
). Inbash
andzsh
, aliases have higher precedence than keywords, so alias redefinitions of keywords do shadow them (but inbash
by default only in interactive shells, unlessshopt -s expand_aliases
is explicitly called). -
Tom Russell almost 8 yearsMind if I bash Perl? Don't you love its clear semantics? LOL.
-
Kevin Olree almost 7 yearsreadlink -f works for me on cygwin and returns a unix-style path.
-
Keymon over 6 yearsI need cd in myreadlink(), because it is a recursive function, going to each directory until it finds a link. If it finds a link, returns the realpath, and then sed would replace the path.
-
Chris Cogdon over 6 yearsActually, the { } around the ( ) are unnecessary, since ( ) counts as a "compound command", just like { }. But you still need () after the function name.
-
Kuro over 6 yearsThis works for me (tested with Sierra) :
while [ -L "${path}" ]; do path=$(readlink "${path}"); done
-
dhill about 6 yearsJust got bitten by it. It doesn't work on Mac and I'm searching for replacement.
-
arr_sea over 5 yearsI prefer
realpath
overreadlink
because it offers option flags such as--relative-to
-
Martin over 5 yearsto bad I look for a way to resolve a file and not a directory.
-
erstaples over 5 yearsAs others have pointed out, this doesn't really answer the question. @pixelbeat's answer below does.
-
Frank Nocke over 4 years@dala You could use this in your script to filter any „trailing“ filename (for ease of mind and paste :)
-
KingxMe about 4 years@ChrisCogdon The
{}
around the()
are not unnecessary if there are no()
behind the function name. Bash accepts function declarations without()
because shell does not have parameter lists in declarations and doesn't do calls with()
so function declarations with()
don't make a lot of sense. -
Michael C. Chen over 3 yearsThis works if the input is a directory, or a file in a symlinked directory and you have a normal file after it.
[-d "$1"] && echo $(cd $1; pwd -P) || echo $(cd $(dirname $1); pwd -P)/$(basename $1) | sed 's=//=/=g'
it does not work if the file itself is a symlink. -
xpt over 2 yearsThis will break if symlink has paths in it, e.g.,
../dir1/dir2/file
-
KingxMe over 2 yearsCan you give an example of a setup where it broke? It ultimately prints the physical path (
pwd -P
) so I'm not sure you mean by broken; it won't always be the shortest possible way to print the path.