How to resolve symbolic links in a shell script

195,268

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.

getcwd pwd

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 halloleodanorton]

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

Share:
195,268

Related videos on Youtube

Greg Hewgill
Author by

Greg Hewgill

Software geek. Twitter: @ghewgill

Updated on May 24, 2020

Comments

  • Greg Hewgill
    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
    Phil Ross about 15 years
    On Debian (etch and later) this command is available in the realpath package.
  • Bkkbrad
    Bkkbrad almost 15 years
    This doesn't work in Mac OS X - see stackoverflow.com/questions/1055671/…
  • dala
    dala about 14 years
    This just works for (the current) directory? If the target is a file, it will not give anything...
  • Tom Howard
    Tom Howard over 12 years
    Doesn't work if the link is broken as you cannot make it your current path
  • jkp
    jkp over 12 years
    shame about OS X incompatibility, otherwise nice +1
  • pixelbeat
    pixelbeat about 12 years
    realpath is now (Jan 2012) part of coreutils and backwards compatible with the debian and BSD variant
  • Kief
    Kief over 11 years
    On OS X you can install coreutils with homebrew. It installs it as "grealpath".
  • halloleo
    halloleo over 11 years
    readlink works on OSX, but needs another syntax: readlink $path without the -f.
  • Magnus
    Magnus almost 11 years
    readlink fails to de-reference multiple layers of symlink though, it just derefs one layer at a time
  • pixelbeat
    pixelbeat almost 11 years
    Both readlink -f and realpath in coreutils, do dereference all "layers" of symlinks
  • blubberdiblub
    blubberdiblub over 10 years
    Even 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
    zdd over 10 years
    are 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
    pixelbeat over 10 years
    No solaris doesn't have these. You would need to build GNU coreutils for your system.
  • toxalot
    toxalot over 10 years
    I 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 without realpath. So it seems to be dependent on more than just version.
  • toxalot
    toxalot over 10 years
    I 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 without realpath. So it seems to be dependent on more than just version.
  • pixelbeat
    pixelbeat over 10 years
    Note 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
    Joe Heyming over 9 years
    Hey 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
    Jonathan Baldwin about 9 years
    Python 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
    Dave over 8 years
    My 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
    Dave over 8 years
    You don't need any more than the sh built-ins readlink, dirname and basename.
  • mklement0
    mklement0 over 8 years
    @Dave: dirname, basename, and readlink are external utilities, not shell built-ins; dirname and basename are part of POSIX, readlink is not.
  • mklement0
    mklement0 over 8 years
    To 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 calling pwd -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 the cd and pwd -P calls in a subshell).
  • mklement0
    mklement0 over 8 years
    This 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
    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
    Dave over 8 years
    If you are concerned with whitespace use quotes.
  • mklement0
    mklement0 over 8 years
    Please do - although that won't address (b).
  • Dave
    Dave over 8 years
    Please 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
    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
    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
    jarno over 8 years
    In my experience, quoting bypasses aliases, not shell functions, unlike you first tell.
  • jarno
    jarno over 8 years
    I tested that I can define [ as an alias in dash and bash and as a shell function in bash.
  • jarno
    jarno over 8 years
    I made my point as a separate question.
  • mklement0
    mklement0 over 8 years
    @jarno: Good points; I've updated my answer; let me know if you think there's still a problem.
  • jarno
    jarno over 8 years
    Just a note: I could not define while as shell function in either bash or dash. I can define while as an alias in bash; thereafter it is recognized as alias (unlike in dash). (Tested by GNU bash, version 4.3.11(1)-release, and dash package version 0.5.7)
  • mklement0
    mklement0 over 8 years
    @jarno: You can define while as a function in bash, ksh, and zsh (but not dash), but only with function <name> syntax: function while { echo foo; } works (while() { echo foo; } doesn't). However, this wont' shadow the while keyword, because keywords have higher precedence than functions (the only way to invoke this function is as \while). In bash and zsh, aliases have higher precedence than keywords, so alias redefinitions of keywords do shadow them (but in bash by default only in interactive shells, unless shopt -s expand_aliases is explicitly called).
  • Tom Russell
    Tom Russell almost 8 years
    Mind if I bash Perl? Don't you love its clear semantics? LOL.
  • Kevin Olree
    Kevin Olree almost 7 years
    readlink -f works for me on cygwin and returns a unix-style path.
  • Keymon
    Keymon over 6 years
    I 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
    Chris Cogdon over 6 years
    Actually, the { } around the ( ) are unnecessary, since ( ) counts as a "compound command", just like { }. But you still need () after the function name.
  • Kuro
    Kuro over 6 years
    This works for me (tested with Sierra) : while [ -L "${path}" ]; do path=$(readlink "${path}"); done
  • dhill
    dhill about 6 years
    Just got bitten by it. It doesn't work on Mac and I'm searching for replacement.
  • arr_sea
    arr_sea over 5 years
    I prefer realpath over readlink because it offers option flags such as --relative-to
  • Martin
    Martin over 5 years
    to bad I look for a way to resolve a file and not a directory.
  • erstaples
    erstaples over 5 years
    As others have pointed out, this doesn't really answer the question. @pixelbeat's answer below does.
  • Frank Nocke
    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
    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
    Michael C. Chen over 3 years
    This 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
    xpt over 2 years
    This will break if symlink has paths in it, e.g., ../dir1/dir2/file
  • KingxMe
    KingxMe over 2 years
    Can 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.