How to obtain the absolute path of a file via Shell (BASH/ZSH/SH)?

#! /bin/sh
echo "$(cd "$(dirname -- "$1")" >/dev/null; pwd -P)/$(basename -- "$1")"

Use realpath

$ realpath example.txt
/home/username/example.txt

Forget about readlink and realpath which may or may not be installed on your system.

Expanding on dogbane's answer above here it is expressed as a function:

#!/bin/bash
get_abs_filename() {
  # $1 : relative filename
  echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}

you can then use it like this:

myabsfile=$(get_abs_filename "../../foo/bar/file.txt")

How and why does it work?

The solution exploits the fact that the Bash built-in pwd command will print the absolute path of the current directory when invoked without arguments.

Why do I like this solution ?

It is portable and doesn't require neither readlink or realpath which often does not exist on a default install of a given Linux/Unix distro.

What if dir doesn't exist?

As given above the function will fail and print on stderr if the directory path given does not exist. This may not be what you want. You can expand the function to handle that situation:

#!/bin/bash
get_abs_filename() {
  # $1 : relative filename
  if [ -d "$(dirname "$1")" ]; then
    echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
  fi
}

Now it will return an empty string if one the parent dirs do not exist.

How do you handle trailing '..' or '.' in input ?

Well, it does give an absolute path in that case, but not a minimal one. It will look like:

/Users/bob/Documents/..

If you want to resolve the '..' you will need to make the script like:

get_abs_filename() {
  # $1 : relative filename
  filename=$1
  parentdir=$(dirname "${filename}")

  if [ -d "${filename}" ]; then
      echo "$(cd "${filename}" && pwd)"
  elif [ -d "${parentdir}" ]; then
    echo "$(cd "${parentdir}" && pwd)/$(basename "${filename}")"
  fi
}

Try readlink which will resolve symbolic links:

readlink -e /foo/bar/baz

Tags:

Shell

Bash

Path