258

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".

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285

21 Answers21

464
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"

mklement0
  • 382,024
  • 64
  • 607
  • 775
pixelbeat
  • 30,615
  • 9
  • 51
  • 60
  • 6
    This doesn't work in Mac OS X - see http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac – Bkkbrad Aug 23 '09 at 20:19
  • 1
    shame about OS X incompatibility, otherwise nice +1 – jkp Nov 29 '11 at 08:16
  • 13
    On OS X you can install coreutils with homebrew. It installs it as "grealpath". – Kief Oct 18 '12 at 14:39
  • 11
    `readlink` works on OSX, but needs another syntax: `readlink $path` *without* the `-f`. – halloleo Jan 28 '13 at 21:00
  • 2
    readlink fails to de-reference multiple layers of symlink though, it just derefs one layer at a time – Magnus Jun 26 '13 at 15:14
  • 1
    Both `readlink -f` and `realpath` in coreutils, do dereference all "layers" of symlinks – pixelbeat Jun 28 '13 at 00:39
  • 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 )` – blubberdiblub Oct 13 '13 at 18:07
  • 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 – zdd Nov 04 '13 at 03:33
  • 2
    No solaris doesn't have these. You would need to build GNU coreutils for your system. – pixelbeat Nov 06 '13 at 20:36
  • I don't have `realpath` on Centos 6 with GNU coreutils 8.4.31. I've come across several others on [Unix & Linux](http://unix.stackexchange.com/) that have a GNU coreutils packaged *without* `realpath`. So it seems to be dependent on more than just version. – toxalot Mar 18 '14 at 22:41
  • 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 https://bugs.debian.org/730779 – pixelbeat Mar 19 '14 at 01:04
  • 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. – Joe Heyming Oct 09 '14 at 22:50
  • Mind if I bash Perl? Don't you love its clear semantics? LOL. – Tom Russell Aug 15 '16 at 06:19
  • readlink -f works for me on cygwin and returns a unix-style path. – Kevin Olree Jul 07 '17 at 18:32
  • This works for me (tested with Sierra) : `while [ -L "${path}" ]; do path=$(readlink "${path}"); done` – Kuro Dec 14 '17 at 18:09
  • @Kuro good idea, but that only tests the last element of the path (basename). It doesn't catch symlinks within the earlier directories of the path. – SpinUp __ A Davis Jul 17 '22 at 06:02
101

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

mklement0
  • 382,024
  • 64
  • 607
  • 775
kauppi
  • 16,966
  • 4
  • 28
  • 19
  • 30
    This just works for (the current) directory? If the target is a file, it will not give anything... – dala Apr 28 '10 at 09:18
  • 5
    Doesn't work if the link is broken as you cannot make it your current path – Tom Howard Nov 16 '11 at 20:17
  • 8
    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 Oct 22 '15 at 20:56
  • to bad I look for a way to resolve a file and not a directory. – Martin Jan 11 '19 at 09:37
  • As others have pointed out, this doesn't really answer the question. @pixelbeat's answer below does. – erstaples Feb 19 '19 at 20:53
  • @dala You could use [this](https://unix.stackexchange.com/a/76228/156470) in your script to filter any „trailing“ filename (for ease of mind and paste :) – Frank N Oct 11 '19 at 10:16
  • 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. – Michael C. Chen Oct 20 '20 at 08:29
28

"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
abcoep
  • 577
  • 9
  • 14
tlrobinson
  • 2,812
  • 1
  • 33
  • 35
23

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.
Gregory
  • 1,479
  • 15
  • 22
  • On Debian (etch and later) this command is available in the realpath package. – Phil Ross Jun 25 '09 at 11:39
  • 2
    realpath is now (Jan 2012) part of coreutils and backwards compatible with the debian and BSD variant – pixelbeat Apr 16 '12 at 07:43
  • 1
    I don't have `realpath` on Centos 6 with GNU coreutils 8.4.31. I've come across several others on [Unix & Linux](http://unix.stackexchange.com/) that have a GNU coreutils packaged *without* `realpath`. So it seems to be dependent on more than just version. – toxalot Mar 18 '14 at 22:43
  • I prefer `realpath` over `readlink` because it offers option flags such as `--relative-to` – arr_sea Dec 12 '18 at 19:05
10
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

Chuck Kollars
  • 2,135
  • 20
  • 18
6

Another way:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 
Keymon
  • 327
  • 3
  • 6
  • 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. – Keymon Oct 19 '17 at 21:03
5

Putting some of the given solutions together, knowing that readlink is available on most systems, but needs different arguments, this works well for me on OSX and Debian. I'm not sure about BSD systems. Maybe the condition needs to be [[ $OSTYPE != darwin* ]] to exclude -f from OSX only.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
hpvw
  • 51
  • 1
  • 2
4

Here's how one can get the actual path to the file in MacOS/Unix using an inline Perl script:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

Similarly, to get the directory of a symlinked file:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
Igor Afanasyev
  • 571
  • 3
  • 9
3

Update:

  • As of at least macOS 13 (Ventura), macOS now also supports readlink -f for following a symlink to its ultimate target (FreeBSD has had support for much longer), so the need for a POSIX-compliant solution has lessened considerably.

  • However, a POSIX-compliant solution - as implemented below - may still be of interest:

    • If you need to support older macOS versions and other Unix-like platforms where readlink -f is not implemented.

    • and/or you need consistent behavior across platforms:

      • Notably, readlink -f on macOS / FreeBSD is not equivalent to the GNU implementation (as found on Linux distros): -f on macOS / FreeBSD corresponds to GNU -e, which enforces the existence of the ultimate target (GNU's -f allows the last path segment of the ultimate target to be non-existent).

Below is a fully POSIX-compliant script / function that is therefore cross-platform (works on macOS too, whose readlink still doesn't support -f as of 10.12 (Sierra)) - it uses only POSIX shell language features and only POSIX-compliant utility calls.

It is a portable implementation of GNU's readlink -e (the stricter version of readlink -f).

You can run the script with sh or source the function in bash, ksh, and zsh:

For instance, inside a script you can use it as follows to get the running's script true directory of origin, with symlinks resolved:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink script / function definition:

The code was adapted with gratitude from this answer.
I've also created a bash-based stand-alone utility version here, which you can install with
npm install rreadlink -g, if you have Node.js installed.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

A tangent on security:

jarno, in reference to the function ensuring that builtin command is not shadowed by an alias or shell function of the same name, asks in a comment:

What if unalias or unset and [ are set as aliases or shell functions?

The motivation behind rreadlink ensuring that command has its original meaning is to use it to bypass (benign) convenience aliases and functions often used to shadow standard commands in interactive shells, such as redefining ls to include favorite options.

I think it's safe to say that unless you're dealing with an untrusted, malicious environment, worrying about unalias or unset - or, for that matter, while, do, ... - being redefined is not a concern.

There is something that the function must rely on to have its original meaning and behavior - there is no way around that.
That POSIX-like shells allow redefinition of builtins and even language keywords is inherently a security risk (and writing paranoid code is hard in general).

To address your concerns specifically:

The function relies on unalias and unset having their original meaning. Having them redefined as shell functions in a manner that alters their behavior would be a problem; redefinition as an alias is not necessarily a concern, because quoting (part of) the command name (e.g., \unalias) bypasses aliases.

However, quoting is not an option for shell keywords (while, for, if, do, ...) and while shell keywords do take precedence over shell functions, in bash and zsh aliases have the highest precedence, so to guard against shell-keyword redefinitions you must run unalias with their names (although in non-interactive bash shells (such as scripts) aliases are not expanded by default - only if shopt -s expand_aliases is explicitly called first).

To ensure that unalias - as a builtin - has its original meaning, you must use \unset on it first, which requires that unset have its original meaning:

unset is a shell builtin, so to ensure that it is invoked as such, you'd have to make sure that it itself is not redefined as a function. While you can bypass an alias form with quoting, you cannot bypass a shell-function form - catch 22.

Thus, unless you can rely on unset to have its original meaning, from what I can tell, there is no guaranteed way to defend against all malicious redefinitions.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • In my experience, quoting bypasses aliases, not shell functions, unlike you first tell. – jarno Mar 10 '16 at 15:11
  • I tested that I can define `[` as an alias in `dash` and `bash` and as a shell function in `bash`. – jarno Mar 10 '16 at 15:19
  • 1
    I made my point as a separate [question](http://stackoverflow.com/q/35916983/4414935). – jarno Mar 10 '16 at 15:23
  • @jarno: Good points; I've updated my answer; let me know if you think there's still a problem. – mklement0 Mar 10 '16 at 15:25
  • 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) – jarno Mar 10 '16 at 15:45
  • 1
    @jarno: You _can_ define `while` as a function in `bash`, `ksh`, and `zsh` (but not `dash`), but only with `function ` 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). – mklement0 Mar 11 '16 at 03:38
  • very good but unfortunately unneded work. there are coreutils described in other hints, that is much enough for resolve symlinks, and much more. Currently (2023 year) all OSes has built in readlink with -f flags , pwd -P (for only current directory) and realpath. Of course, this all except os'es from microsoft and apple. those systems needs install additional GNU software, port of coreutils or similar. – Znik Apr 03 '23 at 13:12
  • 1
    @Znik, please see the top section I've just added to the answer. – mklement0 Apr 03 '23 at 16:20
3

Is your path a directory, or might it be a file? If it's a directory, it's simple:

(cd "$DIR"; pwd -P)

However, if it might be a file, then this won't work:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

because the symlink might resolve into a relative or full path.

On scripts I need to find the real path, so that I might reference configuration or other scripts installed together with it, I use this:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && 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

You could set SOURCE to any file path. Basically, for as long as the path is a symlink, it resolves that symlink. The trick is in the last line of the loop. If the resolved symlink is absolute, it will use that as SOURCE. However, if it is relative, it will prepend the DIR for it, which was resolved into a real location by the simple trick I first described.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
3

In case where pwd can't be used (e.g. calling a scripts from a different location), use realpath (with or without dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Works both when calling through (multiple) symlink(s) or when directly calling the script - from any location.

nakwa
  • 1,157
  • 1
  • 13
  • 25
3

Common shell scripts often have to find their "home" directory even if they are invoked as a symlink. The script thus have to find their "real" position from just $0.

cat `mvn`

on my system prints a script containing the following, which should be a good hint at what you need.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`
Hugo
  • 4,004
  • 1
  • 31
  • 33
2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
Dave
  • 764
  • 7
  • 17
  • 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). – mklement0 Oct 22 '15 at 23:33
  • If you are concerned with whitespace use quotes. – Dave Oct 24 '15 at 03:40
  • Please do - although that won't address (b). – mklement0 Oct 24 '15 at 03:43
  • 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. – Dave Oct 24 '15 at 03:46
  • "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 Oct 24 '15 at 03:54
2

This is a symlink resolver in Bash that works whether the link is a directory or a non-directory:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

Note that all the cd and set stuff takes place in a subshell.

solidsnack
  • 1,631
  • 16
  • 26
  • Actually, the { } around the ( ) are unnecessary, since ( ) counts as a "compound command", just like { }. But you still need () after the function name. – Chris Cogdon Nov 22 '17 at 22:39
  • @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. – solidsnack May 24 '20 at 04:25
  • This will break if symlink has paths in it, e.g., `../dir1/dir2/file` – xpt Jan 06 '22 at 20:33
  • 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. – solidsnack Jan 09 '22 at 05:53
1

To work around the Mac incompatibility, I came up with

echo `php -r "echo realpath('foo');"`

Not great but cross OS

mklement0
  • 382,024
  • 64
  • 607
  • 775
Clemens Tolboom
  • 1,872
  • 18
  • 30
  • 3
    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. – Jonathan Baldwin Mar 27 '15 at 21:31
  • You don't need any more than the sh built-ins readlink, dirname and basename. – Dave Sep 30 '15 at 00:53
  • 1
    @Dave: `dirname`, `basename`, and `readlink` are external _utilities_, not shell built-ins; `dirname` and `basename` are part of POSIX, `readlink` is not. – mklement0 Oct 21 '15 at 18:55
  • @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 Oct 24 '15 at 03:39
  • @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. – mklement0 Oct 24 '15 at 04:04
1

Try this:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
AstroCB
  • 12,337
  • 20
  • 57
  • 73
diyism
  • 12,477
  • 5
  • 46
  • 46
1

Since I've run into this many times over the years, and this time around I needed a pure bash portable version that I could use on OSX and linux, I went ahead and wrote one:

The living version lives here:

https://github.com/keen99/shell-functions/tree/master/resolve_path

but for the sake of SO, here's the current version (I feel it's well tested..but I'm open to feedback!)

Might not be difficult to make it work for plain bourne shell (sh), but I didn't try...I like $FUNCNAME too much. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

here's a classic example, thanks to brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

use this function and it will return the -real- path:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 
keen
  • 816
  • 10
  • 11
  • 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 Sep 30 '15 at 00:51
0

Here I present what I believe to be a cross-platform (Linux and macOS at least) solution to the answer that is working well for me currently.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

Here is a macOS (only?) solution. Possibly better suited to the original question.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}
RuneImp
  • 31
  • 3
0

My answer here Bash: how to get real path of a symlink?

but in short very handy in scripts:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

These are part of GNU coreutils, suitable for use in Linux systems.

To test everything, we put symlink into /home/test2/, amend some additional things and run/call it from root directory:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Where

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Arunas Bartisius
  • 1,879
  • 22
  • 23
0

My 2 cents. This function is POSIX compliant, and both the source and the destination can contain ->. However, I have not gotten it work with filenames that container newline or tabs, as ls in general has issues with those.

resolve_symlink() {
  test -L "$1" && ls -l "$1" | awk -v SYMLINK="$1" '{ SL=(SYMLINK)" -> "; i=index($0, SL); s=substr($0, i+length(SL)); print s }'
}

I believe the solution here is the file command, with a custom magic file that only outputs the destination of the provided symlink.

dxlr8r
  • 1
0

This is the best solution, tested in Bash 3.2.57:

# Read a path (similar to `readlink`) recursively, until the physical path without any links (like `cd -P`) is found.
# Accepts any existing path, prints its physical path and exits `0`, exits `1` if some contained links don't exist.
# Motivation: `${BASH_SOURCE[0]}` often contains links; using it directly to extract your project's path may fail.
#
# Example: Safely `source` a file located relative to the current script
#
#     source "$(dirname "$(rreadlink "${BASH_SOURCE[0]}")")/relative/script.sh"
#Inspiration: https://stackoverflow.com/a/51089005/6307827
rreadlink () {
    declare p="$1" d l
    while :; do
        d="$(cd -P "$(dirname "$p")" && pwd)" || return $? #absolute path without symlinks
        p="$d/$(basename "$p")"
        if [ -h "$p" ]; then
            l="$(readlink "$p")" || break

            #A link must be resolved from its fully resolved parent dir.
            d="$(cd "$d" && cd -P "$(dirname "$l")" && pwd)" || return $?
            p="$d/$(basename "$l")"
        else
            break
        fi
    done
    printf '%s\n' "$p"
}
Andi
  • 457
  • 3
  • 19