77

Is there a Unix command to get the absolute (and canonicalized) path from a relative path which may contain symbolic links?

8 Answers 8

98

You can use the readlink utility, with the -f option:

-f, --canonicalize

      canonicalize  by  following  every symlink in every component of
      the given name recursively; all  but  the  last  component  must
      exist

Some distributions, for example those that use GNU coreutils and FreeBSD, also come with a realpath(1) utility that basically just calls realpath(3) and does pretty much the same thing.

3
  • 20
    The -f switch does not exist on Mac OS X (at least as of 10.8.5). Without the -f switch it just returns an error code.
    – iconoclast
    Sep 27, 2013 at 2:43
  • 5
    Some systems come with neither readlink nor realpath – Solaris and HP-UX, in particular.
    – Sildoreth
    May 22, 2015 at 15:09
  • For the Mac issue: brew install coreutils apple.stackexchange.com/a/69332/23040, and if you don't want to do the step that redirects all standard Mac CLI tools to the newly-installed GNU equivalents, just call greadlink.
    – Adrian
    Jul 18, 2019 at 19:44
28

Portably, the PWD variable is set by the shell to one absolute location of the current directory. Any component of that path may be a symbolic link.

case $f in
  /*) absolute=$f;;
  *) absolute=$PWD/$f;;
esac

If you want to eliminate . and .. as well, change to the directory containing the file and obtain $PWD there:

if [ -d "$f" ]; then f=$f/.; fi
absolute=$(cd "$(dirname -- "$f")"; printf %s. "$PWD")
absolute=${absolute%?}
absolute=$absolute/${f##*/}

There's no portable way to follow symbolic links. If you have a path to a directory, then on most unices $(cd -- "$dir" && pwd -P 2>/dev/null || pwd) provides a path that doesn't use symbolic links, because shells that track symbolic links tend to implement pwd -P (“P” for “physical”).

Some unices provide a utility to print the “physical” path to a file.

  • Reasonably recent Linux systems (with GNU coreutils or BusyBox) have readlink -f, as do FreeBSD ≥8.3, NetBSD ≥4.0, and OpenBSD as far back as 2.2.
  • FreeBSD ≥4.3 has realpath (it's also present on some Linux systems, and it's in BusyBox).
  • If Perl is available, you can use the Cwd module.

    perl -MCwd -e 'print Cwd::realpath($ARGV[0])' path/to/file
    
2
  • Why do you pipe something into pwd ($(cd -- "$dir" && pwd -P 2>/dev/null | pwd))? It doesn't seem to care about stdin. Mar 16, 2017 at 23:39
  • 1
    @MateuszPiotrowski Typo, I meant to write || Mar 17, 2017 at 0:05
7

Is pwd fit for your needs? It gives the absolute path of current directory. Or maybe what you want is realpath().

3

alister and rss67 in this article introduce most stable, compatible and easiest way. I never seen better way than this before.

RELPATH=./../
cd $RELPATH
ABSPATH=`pwd`

If you want to go back to the original location,

ORGPATH=`pwd`
RELPATH=./../
cd $RELPATH
ABSPATH=`pwd`
cd $ORGPATH

or

RELPATH=./../
cd $RELPATH
ABSPATH=`pwd`
cd -

I wish this helps. This was greatest solution for me.

2
  • 15
    That's actually not such a good way. Changing out of a directory and back in is not reliable: you might not have the permission to come back, or the directory might have been renamed in the meantime. Instead, change directories in a subshell: ABSPATH=$(cd -- "$RELPATH" && pwd). Note the double quotes around substitution (so as not to break if the path contains whitespace and globbing characters) and the -- (in case the path begins with -). Also this only works for directories, and doesn't canonicalize symbolic links as requested. See my answer for more details. Sep 27, 2012 at 8:49
  • "> you might not have the permission to come back" You mean cd to a dir but cannot cd back ? Could you please provide some example scenarios. Apr 12, 2019 at 19:10
0

The other answers here were fine but were insufficient for my needs. I needed a solution that I could use in my scripts on any machine. My solution was to write a shell script which I can invoke from the scripts where I need it.

#!/bin/sh
if [ $# -eq 0 ] || [ $# -gt 2 ]; then
  printf 'Usage: respath path [working-directory]\n' >&2
  exit 1
fi
cantGoUp=

path=$1
if [ $# -gt 1 ]; then
  cd "$2"
fi
cwd=`pwd -P` #Use -P option to account for directories that are actually symlinks

#Handle non-relative paths, which don't need resolution
if echo "$path" | grep '^/' > /dev/null ; then
  printf '%s\n' "$path"
  exit 0
fi

#Resolve for each occurrence of ".." at the beginning of the given path.
#For performance, don't worry about ".." in the middle of the path.
while true
do
  case "$path" in
    ..*)
      if [ "$cwd" = '/' ]; then
        printf 'Invalid relative path\n' >&2
        exit 1
      fi
      if [ "$path" = '..' ]; then
        path=
      else
        path=`echo "$path" | sed 's;^\.\./;;'`
      fi
      cwd=`dirname $cwd`
      ;;
    *)
      break
      ;;
  esac
done

cwd=`echo "$cwd" | sed 's;/$;;'`
if [ -z "$path" ]; then
  if [ -z "$cwd" ]; then
    cwd='/'
  fi
  printf '%s\n' "$cwd"
else
  printf '%s/%s\n' "$cwd" "$path"
fi

This solution is written for the Bourne Shell for portability. I have this in a script named "respath.sh".

This script can be used like this:

respath.sh '/path/to/file'

Or like this

respath.sh '/relative/path/here' '/path/to/resolve/relative/to'

The script resolves the path in the first argument using the path from the second argument as the starting point. If only the first argument is provided, then the script resolves the path relative to your current working directory.

5
  • 1
    Note that you'll probably only find a Bourne shell on Solaris 10 and earlier nowadays. That Bourne shell like most Bourne shell implementations since SVR2 (1984) has pwd builtin and doesn't support -P (the Bourne shell did not implement that logical $PWD handling like POSIX shells do). May 22, 2015 at 14:19
  • 1
    If you drop Bourne compatibility, then you can use the POSIX ${var##pattern} syntax and avoid those echo|sed that are going to break with some values. May 22, 2015 at 14:21
  • 1
    Your forget to check the exit status of cd (and pwd). You should probably use cd -P -- as well in case the 1st argument contains some .. May 22, 2015 at 14:22
  • 1
    Your case check should be ..|../*) instead of ..*. May 22, 2015 at 14:23
  • 1
    There's one case of missing quotes in dirname $cwd May 22, 2015 at 14:25
0

In case, additionally, you have some predefined path for $1 (usually ${PWD}), the following would work:

CWD="${1:-${PredefinedAbsolutePath}}"
if [ "${CWD}" != "${PredefinedAbsolutePath}}" ]; then
    case $1 in
        /*) echo "CWD=${CWD}"; ;;
        *) CWD=$(realpath $1); echo "CWD=${CWD}"; ;;
    esac
fi
0

Respecting everyone else answer, I found the function below to be very easier and portable between linux / MAC OSX than others I found everywhere. Might help someone.

#!/usr/bin/bash
get_absolute_path(){
    file_path=`pwd $1`
    echo "$file_path/$1"
}
#to assign to a variable
absolute_path=$(get_absolute_path "file.txt")
echo $absolute_path
-1

Assume variable a stores a path name (a="whatever")

1. For potential space-containing path:

c=$(grep -o " " <<< $a | wc -l); if (( $c > "0" )); then a=$(sed 's/ /\\ /g' <<< $a); fi

2. For the actual question, i.e. converting path to absolute: readlink can retrieve symlink contents, which can be employed as follows. (note readlink -f would have been perfect but is not available in at least Mac OS X bash, in which -f stands for FORMATS.)

if [[ $(readlink $a) == "" ]]; then a=$(cd $a; pwd); else a=$(cd $(readlink $a); pwd); fi

3. Just learned pwd -P in man pwd: -P is to "Display the physical current working directory (all symbolic links resolved)" and hence

if (( $(grep -o " " <<< $a | wc -l) > "0" )); then a=$(sed 's/ /\\ /g' <<< $a); fi; a=$(cd $a; pwd -P)

shall work, too.

Conclusion: combine 1 with 2 to satisfy both of your requirements, while space-containing path names are already taken care of in the more concise 3.

3
  • You gu(r)ess wrong. E.g. if your path includes a space most of the above wont work.
    – Anthon
    Jan 15, 2017 at 9:36
  • or a glob char sometimes. And it doesn't resolve symlinks, as was requested. Jan 15, 2017 at 13:38
  • I've deleted my "comment" response regarding path names containing spaces and modified the "answer" part accordingly.
    – vxtal
    Jan 16, 2017 at 22:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .