A realpath Implementation in Bash ¬

2012-02-19

I was recently informed of an issue with the in-development version of trash (one of the utilities in tools-osx) that required using an absolute path internally instead of a relative path. In most languages one can just run a path through realpath() to get the absolute path for a relative path, but bash (which trash was developed in) doesn’t have an equivalent. Many people suggest readlink, but it’s generally only included in Linux distributions, so BSD-based operating systems (incl. Mac OS X) are a bit out of luck.

Fortunately, it’s quite easy to emulate using pwd, but there is a bit of extra work that must be done. I found a good, portable example, but it still wasn’t quite up to my standards. I’ve simplified & improved it and the result is as follows (last updated 2012-11-29):

function realpath()
{
	local success=true
	local path="$1"

	# make sure the string isn't empty as that implies something in further logic
	if [ -z "$path" ]; then
		success=false
	else
		# start with the file name (sans the trailing slash)
		path="${path%/}"

		# if we stripped off the trailing slash and were left with nothing, that means we're in the root directory
		if [ -z "$path" ]; then
			path="/"
		fi

		# get the basename of the file (ignoring '.' & '..', because they're really part of the path)
		local file_basename="${path##*/}"
		if [[ ( "$file_basename" = "." ) || ( "$file_basename" = ".." ) ]]; then
			file_basename=""
		fi

		# extracts the directory component of the full path, if it's empty then assume '.' (the current working directory)
		local directory="${path%$file_basename}"
		if [ -z "$directory" ]; then
			directory='.'
		fi

		# attempt to change to the directory
		if ! cd "$directory" &>/dev/null ; then
			success=false
		fi

		if $success; then
			# does the filename exist?
			if [[ ( -n "$file_basename" ) && ( ! -e "$file_basename" ) ]]; then
				success=false
			fi

			# get the absolute path of the current directory & change back to previous directory
			local abs_path="$(pwd -P)"
			cd "-" &>/dev/null

			# Append base filename to absolute path
			if [ "${abs_path}" = "/" ]; then
				abs_path="${abs_path}${file_basename}"
			else
				abs_path="${abs_path}/${file_basename}"
			fi

			# output the absolute path
			echo "$abs_path"
		fi
	fi

	$success
}

Here’s a simple example of its usage:

relative_path="../Shared/"
absolute_path="$(realpath "$relative_path")"
if [ $? -eq 0 ]; then
	echo "absolute_path = $absolute_path"
else
	echo "'$relative_path' does not exist!"
fi

I have also tossed together a realpath tool which wraps around this implementation if you would like to install it for use in multiple scripts/tools. Feel free to download it from the development page or get the source code on GitHub. It’s released under a BSD license.

  Textile help