How to retrieve the absolute path of an arbitrary file from the OS X

14,314

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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

Solution 5

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"'
Share:
14,314

Related videos on Youtube

Michael Wehner
Author by

Michael Wehner

Updated on September 17, 2022

Comments

  • Michael Wehner
    Michael Wehner over 1 year

    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.

  • Michael Wehner
    Michael Wehner over 13 years
    I think you're on the right track with the first shell-based approach, but I believe that using another language for doing this somewhat defeats the purpose, as it introduces additional dependencies and is pretty much equivalent to simply compiling GNU's version of `readlink(1)' under OS X (assuming this can be done; I haven't verified it yet).
  • Michael Wehner
    Michael Wehner over 13 years
    My earlier comment on Daniel Beck's answer applies here as well (I'd prefer a purely Bash-y solution), though if I were to resort to using another language achieve this, I like your solution best so far for its brevity. :) I'd also probably wrap the call to ruby (e.g. function abspath () { ruby -e "puts File.expand_path('$1')"; }) and put it into my `.profile'.
  • HikeMike
    HikeMike over 13 years
    @Michael You're either on a system with GNU readlink, or on OS X -- right? OS X has Python out of the box. In theory it's a new dependency, in practice not. Anyway, it's all I can offer you.
  • Michael Wehner
    Michael Wehner over 13 years
    The pragmatic approach you're implying is laudable, if overly simplistic. In any case, if we were to take this approach in lieu of anything written purely in bash (which is absolutely doable and doesn't depend on anything else--it just can't easily be done in one line), Python probably isn't the best choice. Both Perl and Ruby (and probably PHP) can do this succinctly on the command line without the need to create an actual script file.
  • HikeMike
    HikeMike over 13 years
    @Michael: True, but that's not what I was commenting on. I offer you a 90% solution written in pure bash by Jesse Wilson + the analysis why it's only 90%. If that's no problem for you, that's fine. I also gave you a slightly more complicated, 100% solution in Python. While other scripting languages might be briefer (Perl infamously so :-) ), all require an additional file to store the command. Or do you want to write it out every time you want to use it? That's also why I added the multi-line ability to handle multiple parameters, 1 or 2 lines then don't make a different.
  • Arjan
    Arjan over 13 years
    @Michael, why wouldn't Python be a good choice on OS X? It even ships with commands that are actually Python scripts, like file /usr/bin/xattr
  • brevno
    brevno almost 12 years
    Some variables and command substitutions aren't quoted properly. You could use local variables instead of variable names like __filename. The script currently behaves more like dirname anyway.
  • JayB
    JayB over 4 years
    The python example is a fantastic solution, and I've been using it a lot. But python will be gone from macOS starting with macOS 10.16, and you can only get it back, if you install Xcode or the CLTs, but that might yield python3. So my question: is it possible to do the same as os.path.realpath(os.path.abspath(arg)) with python3?
  • JayB
    JayB over 4 years
    Found it myself… only need additional brackets: print(os.path.realpath(os.path.abspath(arg)))