20

I'm looking for a simple command that can be used within Bash to find the absolute and canonicalized path to a file on an OS X (similar to ``readlink -f'` under Linux).

The following sample bash session describes a [fictitious] utility called ``abspath'` that exhibits the desired behavior:

$ pwd
/Users/guyfleegman

$ ls -lR
drwxr-xr-x  4 guyfleegman  crew  136 Oct 30 02:09 foo

./foo:
-rw-r--r--  1 guyfleegman  crew  0 Oct 30 02:07 bar.txt
lrwxr-xr-x  1 guyfleegman  crew  7 Oct 30 02:09 baz.txt -> bar.txt


$ abspath .
/Users/guyfleegman

$ abspath foo
/Users/guyfleegman/foo

$ abspath ./foo/bar.txt
/Users/guyfleegman/foo/bar.txt

$ abspath foo/baz.txt
/Users/guyfleegman/foo/baz.txt

As with the last invocation of ``abspath'` in the above example, I'd prefer it didn't automatically resolve symlinks, but I'm not going to be too picky here.

9 Answers9

16
function abspath() { pushd . > /dev/null; if [ -d "$1" ]; then cd "$1"; dirs -l +0; else cd "`dirname \"$1\"`"; cur_dir=`dirs -l +0`; if [ "$cur_dir" == "/" ]; then echo "$cur_dir`basename \"$1\"`"; else echo "$cur_dir/`basename \"$1\"`"; fi; fi; popd > /dev/null; }

Examples:

abspath / => /

abspath /.DS_Store => /.DS_Store

abspath ~ => /Users/mschrag

cd /tmp; abspath . => /tmp

cd /; abspath .DS_Store => /.DS_Store
Gareth
  • 19,080
mschrag
  • 161
12

I don't think there's a buildin command that does this. Jesse Wilson wrote a bash script for this:

#!/bin/bash
cd -P -- "$(dirname -- "$1")" &&
printf '%s\n' "$(pwd -P)/$(basename -- "$1")"

However, it does not work well for paths directly below /, such as /etc (printing //etc), as well as . and .. (printing /cwd/. in both cases). I tried modifying it, but my unsufficient bash-fu failed me.

Here's my suggestion:

#!/usr/bin/env python
import os.path
import sys

for arg in sys.argv[1:]:
    print os.path.abspath(arg)

Save as /usr/bin/abspath or something like that and make it executable. Sample output:

Servus08:~ danielbeck$ abspath .
/Users/danielbeck
Servus08:~ danielbeck$ abspath /tmp
/tmp
Servus08:~ danielbeck$ abspath Documents
/Users/danielbeck/Documents
Servus08:~ danielbeck$ abspath . /tmp Documents
/Users/danielbeck
/tmp
/Users/danielbeck/Documents

If you do want symlink resolution, change the print line like this:

    print os.path.realpath(os.path.abspath(arg))

to get this:

Servus08:~ danielbeck$ abspath . /tmp Documents
/Users/danielbeck
/private/tmp
/Users/danielbeck/Documents
Daniel Beck
  • 111,893
9

One option would be to just install coreutils and use greadlink -f. It resolves symlinks and it works with /Foo/ or ~/foo.txt if they don't exist, but not with /Foo/foo.txt if /Foo/ doesn't exist.

$ brew install coreutils
$ greadlink -f /etc
/private/etc
$ greadlink -f ~/Documents/
/Users/lauri/Documents
$ greadlink -f ..
/Users
$ greadlink -f //etc/..////
/private
$ greadlink -f /Foo
/Foo
$ greadlink -f /Foo/foo.txt
$ 

This doesn't resolve symlinks, and it doesn't work with /Foo/foo.txt either.

abspath() {
  if [ -d "$1" ]; then
    ( cd "$1"; dirs -l +0 )
  else
    ( cd "$(dirname "$1")"; d=$(dirs -l +0); echo "${d%/}/${1##*/}" )
  fi
}

abspath /etc # /etc
abspath ~/Foo/foo.txt # doesn't work
abspath ~/Foo # works
abspath .
abspath ./
abspath ../
abspath ..
abspath /
abspath ~
abspath ~/
abspath ~/Documents
abspath /\"\ \'
abspath /etc/../etc/
abspath /private//etc/
abspath /private//
abspath //private # //private
abspath ./aa.txt
abspath aa.tar.gz
abspath .aa.txt
abspath /.DS_Store
abspath ~/Documents/Books/

dirs -l performs tilde expansion. dirs +0 prints only the topmost directory if there are other directories in the stack.

Lri
  • 42,502
  • 8
  • 126
  • 159
3

I guess you could do it with either python or ruby.

$ ruby -e 'puts File.expand_path("~/somepath")'

or make it a command with

#!/usr/bin/env ruby
puts File.expand_path(ARGV[0])
Jay
  • 684
  • 1
  • 5
  • 12
1

If you have the File::Spec module installed for perl you can just do this:

perl -MFile::Spec -e 'print File::Spec->rel2abs("../however/complex/../you/want/to.get"), "\n"'
Dodger
  • 11
0

If installing coreutils is not an option, the following handles combos of symlinks, . and .. and works on files and folders like GNU realpath does:

#!/usr/bin/env bash
realpath()
{
    if ! pushd $1 &> /dev/null; then 
        pushd ${1##*/} &> /dev/null
        echo $( pwd -P )/${1%/*}
    else
        pwd -P
    fi
    popd > /dev/null
}

But it does not support realpath's --relative-to. This would require https://stackoverflow.com/a/12498485/869951.

Oliver
  • 143
0

Given that the constraint is MacOS (OS X at the time), PHP is available by default. This will return the root directory of the file. Remove dirname to get the file, too.

export SOURCE_DIRECTORY="$(php -r 'echo dirname(realpath($argv[1]));' -- "${BASH_SOURCE[0]}")"
0

For bash/sh scripts you can use this recursive function:

canonical_readlink ()
{
    cd `dirname $1`;
    __filename=`basename $1`;
    if [ -h "$__filename" ]; then
        canonical_readlink `readlink $__filename`;
    else
        echo "`pwd -P`";
    fi
}

answer=$(canonical_readlink $0)
0

Install the following library for OSX:

brew install coreutils

greadlink -f file.txt
anh_ng8
  • 181
  • 1
  • 4