Converting relative path into absolute path?

PerlUnixShellPathAbsolute Path

Perl Problem Overview


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

Perl Solutions


Solution 1 - Perl

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.

Solution 2 - Perl

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

Solution 3 - Perl

Using [tag:bash]

# 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 [tag:perl]

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

Solution 4 - Perl

Take a look at 'realpath'.

$ realpath

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

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

/data/home

Solution 5 - Perl

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

Solution 6 - Perl

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

Solution 7 - Perl

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjoshView Question on Stackoverflow
Solution 1 - PerlbukzorView Answer on Stackoverflow
Solution 2 - PerlDVKView Answer on Stackoverflow
Solution 3 - PerloHoView Answer on Stackoverflow
Solution 4 - PerlSteveMcView Answer on Stackoverflow
Solution 5 - PerlkeenView Answer on Stackoverflow
Solution 6 - PerlcustomcommanderView Answer on Stackoverflow
Solution 7 - PerlRohanView Answer on Stackoverflow