64

I'm not sure if these paths are duplicates. Given the relative path, how do I determine absolute path using a shell script?

Example:

relative path: /x/y/../../a/b/z/../c/d

absolute path: /a/b/c/d
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
josh
  • 13,793
  • 12
  • 49
  • 58
  • 15
    Your "relative" path is actually an absolute path, but it's not in canonical form. A relative path never starts with a `/`. Maybe search SO for "canonical path". – Stephen P Oct 28 '10 at 17:03
  • possible duplicate of [bash/fish command to print absolute path to a file](http://stackoverflow.com/questions/3915040/bash-fish-command-to-print-absolute-path-to-a-file) – Trevor Boyd Smith May 05 '15 at 16:13
  • Does this answer your question? [How to retrieve absolute path given relative](https://stackoverflow.com/questions/4175264/how-to-retrieve-absolute-path-given-relative) – Karl Knechtel Mar 15 '23 at 05:22

7 Answers7

59

The most reliable method I've come across in unix is readlink -f:

$ readlink -f /x/y/../../a/b/z/../c/d
/a/b/c/d

A couple caveats:

  1. This also has the side-effect of resolving all symlinks. This may or may not be desirable, but usually is.
  2. readlink will give a blank result if you reference a non-existant directory. If you want to support non-existant paths, use readlink -m instead. Unfortunately this option doesn't exist on versions of readlink released before ~2005.
bukzor
  • 37,539
  • 11
  • 77
  • 111
  • 9
    [readlink -f is not available on OS X](http://superuser.com/questions/205127/how-to-retrieve-the-absolute-path-of-an-arbitrary-file-from-the-os-x). – Lri Aug 21 '12 at 20:14
  • @jared-beck Can you show that `readlink -f` does the same thing? In my (OS X El Capitan) installation `readlink -f` first of all is basically `stat -f` and that shows: `-f Display information using the specified format. See the FORMATS section for a description of valid formats.`. Maybe you installed `coreutils`? – Kreisquadratur Nov 27 '15 at 14:08
  • 1
    Currently, OS X El Capitan has a different readlink command that can find the real path of a symbolic link, rather than resolve relative paths to absolute ones. So if we want to be portable, it looks like we're stuck with changing the directory and then going back. – Spencer Williams Feb 18 '16 at 04:26
50

From this source comes:

#!/bin/bash

# Assume parameter passed in is a relative path to a directory.
# For brevity, we won't do argument type or length checking.

ABS_PATH=`cd "$1"; pwd` # double quotes for paths that contain spaces etc...
echo "Absolute path: $ABS_PATH"

You can also do a Perl one-liner, e.g. using Cwd::abs_path

trejder
  • 17,148
  • 27
  • 124
  • 216
DVK
  • 126,886
  • 32
  • 213
  • 327
  • 4
    Very nice. And what if file name is also given. like /x/y/../../a/b/z/../c/d.txt => /a/b/c/d.txt – josh Oct 28 '10 at 17:09
  • 1
    The first one fails on paths containing spaces (though interestingly a path like `foo; echo BAR` doesn't misbehave in the way I expected). -1 to get your attention... – j_random_hacker Feb 03 '11 at 15:02
  • @j_random_hacker - (1) fixed; (2) users get notifications for new comments to their answers, no need to use votes to get attention :) – DVK Feb 03 '11 at 15:23
  • +2 :) I know, "get your attention" are weasel words -- "supply motivation" is more like it... I do it as a general policy now as I've had decidedly mixed results with just commenting unfortunately. – j_random_hacker Feb 03 '11 at 15:29
  • 4
    Interestingly `Cwd::abs_path` will fail if the specified path does not exist. – RJFalconer Feb 26 '13 at 09:04
  • @oberlies - while you're of course free to vote for any reasons you wish whatsoever, having a disagreement on a perfectly appropriate and standard on SO style of formatting is not generally considered one of the **appropriate** reasons to downvote. Feel free to ask on Meta if you disagree. – DVK Jun 13 '13 at 10:51
  • @oberlies - as a matter of fact, someone - presumably you - tried to edit the post **twice** today, and the edits were apparently NOT approved (as the post isn't edited) - meaning that two separate reviewers who aren't me fully disagree with your opinion of the fact that your preference is better. – DVK Jun 13 '13 at 10:53
  • The `cd "$1"; pwd` is quite bad because if `cd` fail (if `$1` is not a valid directory), then `pwd` will give the current path and `ABS_PATH` will contain the error message from `cd` and the current path (yeurk!). A more stable code is `ABS_PATH="$(cd "$1" > /dev/null 2>&1 && pwd)", where `&&` ensures that `pwd` will be executed if and only if `cd` succeed, otherwise, `ABS_PATH` will just be empty. – lavalade Sep 11 '20 at 11:22
21

Using

# Directory
relative_dir="folder/subfolder/"
absolute_dir="$( cd "$relative_dir" && pwd )"

# File
relative_file="folder/subfolder/file"
absolute_file="$( cd "${relative_file%/*}" && pwd )"/"${relative_file##*/}"
  • ${relative_file%/*} is same result as dirname "$relative_file"
  • ${relative_file##*/} is same result as basename "$relative_file"

Caveats: Does not resolve symbolic links (i.e. does not canonicalize path ) => May not differentiate all duplicates if you use symbolic links.


Using realpath

Command realpath does the job. An alternative is to use readlink -e (or readlink -f). However realpath is not often installed by default. If you cannot be sure realpath or readlink is present, you can substitute it using perl (see below).


Using

Steven Kramer proposes a shell alias if realpath is not available in your system:

$ alias realpath="perl -MCwd -e 'print Cwd::realpath(\$ARGV[0]),qq<\n>'"
$ realpath path/folder/file
/home/user/absolute/path/folder/file

or if you prefer using directly perl:

$ perl -MCwd -e 'print Cwd::realpath($ARGV[0]),qq<\n>' path/folder/file
/home/user/absolute/path/folder/file

This one-line perl command uses Cwd::realpath. There are in fact three perl functions. They take a single argument and return the absolute pathname. Below details are from documentation Perl5 > Core modules > Cwd.

  • abs_path() uses the same algorithm as getcwd(). Symbolic links and relative-path components (. and ..) are resolved to return the canonical pathname, just like realpath.

    use Cwd 'abs_path';
    my $abs_path = abs_path($file);
    
  • realpath() is a synonym for abs_path()

    use Cwd 'realpath';
    my $abs_path = realpath($file);
    
  • fast_abs_path() is a more dangerous, but potentially faster version of abs_path()

    use Cwd 'fast_abs_path';
    my $abs_path = fast_abs_path($file);
    

These functions are exported only on request => therefore use Cwd to avoid the "Undefined subroutine" error as pointed out by arielf. If you want to import all these three functions, you can use a single use Cwd line:

use Cwd qw(abs_path realpath fast_abs_path);
Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200
  • +1, but could you make this more complete by adding `use Cwd qw(abs_path realpath fast_abs_path);` before the examples? As is, all 3 examples produce "Undefined subroutine" errors. – arielf Aug 06 '14 at 00:31
  • Thank you @arielf very much for your suggestion. I hope the reworked answer fits your dream :-) Any other improvement? Cheers ;-) – oHo Aug 28 '14 at 13:21
  • 3
    The one-line version for distributions with a decent Perl but no `realpath` installation: `alias realpath="perl -MCwd -e 'print Cwd::realpath ($ARGV[0]), qq<\n>'"` – Steven Kramer Sep 25 '14 at 08:14
  • Thanks for your idea to use `perl` as `realpath` replacement. I have integrate your one-line command in the answer to be useful for other SO readers. Cheers ;-) (I have also upvoted some of your answers as reward) – oHo Sep 26 '14 at 08:57
  • @StevenKramer @olibre This returns an output like: `/etc/ARRAY(0x9afa68)` instead of `/etc/file`, how can I print the file name instead of the ARRAY dump? – Mark Feb 20 '15 at 00:30
  • Hi @Mark. What is your perl's version? What is your Linux distribution? (or BSD/MAC/Windows...) ? Please provide a shell example including the creation of the file in order to reproduce your issue. Cheers ;-) – oHo Feb 20 '15 at 09:26
  • 1
    @olibre MacOSX, Perl v5.18.2. Script at: https://gist.github.com/thefosk/43296cb8bf74497c9d69 – Mark Feb 20 '15 at 21:39
  • 1
    @Mark your shell is expanding `$ARGV` in the definition of the alias, so that `Perl sees Cwd::realpath ([0])`, which is an array ref. Escape `$ARGV` in the alias. zsh doesn't have this problem. – Steven Kramer Mar 06 '15 at 21:56
  • Thank you @StevenKramer for your contribution (I could not reproduce the issue) :-) I may have to improve the answer if this might happen often... Do you suggest something to improve the answer... Have a nice week-end. Cheers. – oHo Mar 06 '15 at 22:16
  • 1
    See my edit, which escape the alias and should work for bash/zsh/ksh. – Steven Kramer Mar 08 '15 at 18:53
  • Perfect :) I will upvote some of your answers to reward you as this answer is also yours ;) Cheers – oHo Mar 08 '15 at 22:10
20

Take a look at 'realpath'.

$ realpath

usage: realpath [-q] path [...]

$ realpath ../../../../../

/data/home
Toto
  • 89,455
  • 62
  • 89
  • 125
SteveMc
  • 1,386
  • 8
  • 11
1

Since I've run into this many times over the years, and this time around I needed a pure bash portable version that I could use on OSX and linux, I went ahead and wrote one:

The living version lives here:

https://github.com/keen99/shell-functions/tree/master/resolve_path

but for the sake of SO, here's the current version (I feel it's well tested..but I'm open to feedback!)

Might not be difficult to make it work for plain bourne shell (sh), but I didn't try...I like $FUNCNAME too much. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

here's a classic example, thanks to brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

use this function and it will return the -real- path:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
keen
  • 816
  • 10
  • 11
0

I wanted to use realpath but it is not available on my system (macOS), so I came up with this script:

#!/bin/sh

# NAME
#   absolute_path.sh -- convert relative path into absolute path
#
# SYNOPSYS
#   absolute_path.sh ../relative/path/to/file

echo "$(cd $(dirname $1); pwd)/$(basename $1)"

Example:

./absolute_path.sh ../styles/academy-of-management-review.csl 
/Users/doej/GitHub/styles/academy-of-management-review.csl
customcommander
  • 17,580
  • 5
  • 58
  • 84
-1

May be this helps:

$path = "~user/dir/../file" 
$resolvedPath = glob($path); #   (To resolve paths with '~')
# Since glob does not resolve relative path, we use abs_path 
$absPath      = abs_path($path);
Rohan
  • 7
  • 1