How to determine the absolute path to a file in a shell script

command linescript

I am writing a generic script that can get passed both absolute and relative (to the current working dir) paths to existing files.

I need a fail-safe way to turn all those passed paths into absolute paths because I need to pass the paths to the "open" command, which in won't pass the current directory to the launched app.

One idea I had is to check if the path starts with a "/", and if not, then I'd prepend the current working dir's path to it.

However, I wonder if there's a smarter solution to this. Also, as I'm quite inexperienced with shell scripting and related tools, I don't even know how to test for a variable's value to start with "/". I'm looking for a portable solution to run on multiple Mac, so installing extra tools or configuration would be less than ideal.

Can someone suggest a standard script using only shipping tools that takes a variable (let's call it $path) and turns it into a absolute path if it's not already absolute?

Best Answer

One option would be to 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

dirs -l performs tilde expansion. dirs +0 prints only the top directory if there are other directories in the stack. You could also replace the subshells with something like old="$PWD" ... cd "$old".

Ruby's expand_path works with all paths that don't exist, but it doesn't resolve symlinks.

$ ruby -e 'print File.expand_path ARGV[0]' ~/aa/bb
/Users/lauri/aa/bb$