tags:

views:

1494

answers:

8

On Linux, the readlink utility accepts an option -f that follows additional links. This doesn't seem to work on Mac and possibly BSD based systems. What would the equivalent be?

Here's some debug information:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
A: 

The paths to readlink are different between my system and yours. Please try specifying the full path:

/sw/sbin/readlink -f

ennuikiller
And what - exactly - is the difference between /sw/sbin and /usr/bin? And why do the two binaries differ?
troelskn
http://www.finkproject.org/doc/packaging/fslayout.php
ennuikiller
Aha .. so fink contains a replacement for `readlink` that is gnu compatible. That's nice to know, but it doesn't solve my problem, since I need my script to run on other peoples machine, and I can't require them to install fink for that.
troelskn
A: 

maybe an awk script can do it for you?

I don't think so. I could use another general purpose programming or scripting language, but I'd prefer something more lightweight.
troelskn
A: 

The readlink from linux it is not the same in MacOSX:

STAT(1)                   BSD General Commands Manual                  STAT(1)

NAME
     readlink, stat -- display file status

SYNOPSIS
     stat [-FLnq] [-f format | -l | -r | -s | -x] [-t timefmt] [file ...]
     readlink [-n] [file ...]

DESCRIPTION
     The stat utility displays information about the file pointed to by file.
     Read, write or execute permissions of the named file are not required,
     but all directories listed in the path name leading to the file must be
     searchable.  If no argument is given, stat displays information about the
     file descriptor for standard input.

     When invoked as readlink, only the target of the symbolic link is
     printed.  If the given argument is not a symbolic link, readlink will
     print nothing and exit with an error.

What is readlink -f supposed to do on Linux?

eledu81
http://www.gnu.org/software/coreutils/manual/coreutils.html#readlink-invocation
troelskn
+6  A: 

You may be interested in realpath(3), or Python's os.path.realpath. The two aren't exactly the same; the C library call requires that intermediary path components exist, while the Python version does not.

$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r--  1 miles    wheel  0 Jul 11 21:08 a
lrwxr-xr-x  1 miles    wheel  1 Jul 11 20:49 b -> a
lrwxr-xr-x  1 miles    wheel  1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print os.path.realpath(sys.argv[1])' c
/private/tmp/foo/a

I know you said you'd prefer something more lightweight than another scripting language, but just in case compiling a binary is insufferable, you can use Python and ctypes (available on Mac OS X 10.5) to wrap the library call:

#!/usr/bin/python

import ctypes, sys

libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p

def realpath(path):
    buffer = ctypes.create_string_buffer(1024) # PATH_MAX
    if libc.realpath(path, buffer):
        return buffer.value
    else:
        errno = libc.__error().contents.value
        raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))

if __name__ == '__main__':
    print realpath(sys.argv[1])

Ironically, the C version of this script ought to be shorter. :)

Miles
Yes, `realpath` is indeed what I want. But it seems rather awkward that I have to compile a binary to get this function from a shell script.
troelskn
Why not use the Python one-liner in the shell script then? (Not so different from a one-line call to `readlink` itself, is it?)
Telemachus
+2  A: 

"readlink -f" does two things:

  1. It iterates along a sequence of symlinks until it finds an actual file.
  2. It returns that file's canonicalized name---i.e., its absolute pathname.

If you want to, you can just build a shell script that uses vanilla readlink behavior to achieve the same thing. Here's an example. Obviously you could insert this in your own script where you'd like to call "readlink -f":

#!/bin/sh

TARGET_FILE=$1

cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`

# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
    TARGET_FILE=`readlink $TARGET_FILE`
    cd `dirname $TARGET_FILE`
    TARGET_FILE=`basename $TARGET_FILE`
done

# Compute the canonicalized name by finding the physical path 
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT

Note that this doesn't include any error handling. Of particular importance, it doesn't detect symlink cycles. A simple way to do this would be to count the number of times you go around the loop and fail if you hit an improbably large number, such as 1,000.

EDITED to use 'pwd -P' instead of $PWD.

Keith Smith
As far as I can tell, that won't work if a parent dir of the path is a symlink. Eg. if `foo -> /var/cux`, then `foo/bar` won't be resolved, because `bar` isn't a link, although `foo` is.
troelskn
Ah. Yes. It's not as simple but you can update the above script to deal with that. I'll edit (rewrite, really) the answer accordingly.
Keith Smith
Well, a link could be anywhere in the path. I guess the script could iterate over each part of the path, but it does become a bit complicated then.
troelskn
A link earlier in the path shouldn't matter. All of the tools and system calls that operate on paths automatically follow symlinks in pathnames. In testing on my system, the above script and "readlink -f" produce the same results when there is a symlink in the middle of a path---either the argument or in another symlink. Can you provide an example where it's a problem?
Keith Smith
Yes: `mkdir a; mkdir a/b; mkdir x; ln -s ../a x/y`. Now assuming that the above script is `canonicalize`, running `./canonicalize x/y/b` does not give the same output as `readlink -f x/y/b`.
troelskn
Thanks. The problem is that $PWD is giving us the logical working directory, based in the values in the symlinks that we've followed. We can get the real physical directory with 'pwd -P' It should compute it by chasing ".." up to the root of the file system. I'll update the script in my answer accordingly.
Keith Smith
Aha. `pwd -P` does it. And from what I can tell, it works on bsd/mac. Thanks for your effort.
troelskn
Glad to help out. You had a question that tickled my fancy.
Keith Smith
A: 

I made a script called realpath personally which looks a little something like:

#!/usr/bin/env python
import os,sys
print os.path.realpath(sys.argv[0])
James
+1  A: 

Here is a portable shell function that should work in ANY Bourne comparable shell. It will resolve the relative path punctuation ".. or ." and dereference symbolic links.

If for some reason you do not have a realpath(1) command, or readlink(1) this can be aliased.

which realpath || alias realpath='real_path'

Enjoy:

real_path () {
  OIFS=$IFS
  IFS='/'
  for I in $1
  do
    # Resolve relative path punctuation.
    if [ "$I" = "." ] || [ -z "$I" ]
      then continue
    elif [ "$I" = ".." ]
      then FOO="${FOO%%/${FOO##*/}}"
           continue
      else FOO="${FOO}/${I}"
    fi

    ## Resolve symbolic links
    if [ -h "$FOO" ]
    then
    IFS=$OIFS
    set `ls -l "$FOO"`
    while shift ;
    do
      if [ "$1" = "->" ]
        then FOO=$2
             shift $#
             break
      fi
    done
    IFS='/'
    fi
  done
  IFS=$OIFS
  echo "$FOO"
}

also, just in case anybody is interested here is how to implement basename and dirname in 100% pure shell code:

## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
  echo "${1%/*}"
}

## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
  echo "${1##*/}"
}

You can find updated version of this shell code at my google site: http://sites.google.com/site/jdisnard/realpath

masta
A: 

The coreutils package of MacPorts provides greadlink, which is GNU readlink. credit to Michael Kallweitt post in mackb.com

tomyjwu