A portable, pure shell implementation of realpath
Copy the functions in realpath.sh into your shell script to
avoid introducing a dependency on either realpath
or readlink -f
, since:
realpath
does not come installed by defaultreadlink -f
is not portable to OS-X
$ brew install coreutils
$ greadlink -f $FILE
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
$ source ./realpath.sh
$ realpath /proc/self
/proc/2772
Or we can get tricky:
$ cd /tmp
$ mkdir -p somedir/targetdir somedir/anotherdir
$ ln -s somedir somedirlink
$ ln -s somedir/anotherdir/../anotherlink somelink
$ ln -s targetdir/targetpath somedir/anotherlink
$ realpath .///somedirlink/././anotherdir/../../somelink
/tmp/somedir/targetdir/targetpath
Note: unlike realpath(1)
, these functions take no options; do not use --
to escape any arguments
Function | Description |
---|---|
realpath PATH |
Resolve all symlinks to PATH , then output the canonicalized result |
resolve_symlinks PATH |
If PATH is a symlink, follow it as many times as possible; output the path of the first non-symlink found |
canonicalize_path PATH |
Output absolute path that PATH refers to, resolving any relative directories (. , .. ) in PATH and any symlinks in PATH 's ancestor directories |
realpath.sh
includes optional readlink emulation. It exposes a readlink
function that calls the system readlink(1)
if it exists. Otherwise it uses
stat(1)
to emulate the same functionality. In contrast to the functions in
the previous section, you may pass --
as the first argument, since you may be
calling the system readlink(1)
.
readlink -f
does two things
- resolves symlinks recursively
- canonicalizes the result, hence:
realpath() {
canonicalize_path "$(resolve_symlinks "$1")"
}
First, the symlink resolver implementation:
resolve_symlinks() {
local dir_context path
path=$(readlink -- "$1")
if [ $? -eq 0 ]; then
dir_context=$(dirname -- "$1")
resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
else
printf '%s\n' "$1"
fi
}
_prepend_path_if_relative() {
case "$2" in
/* ) printf '%s\n' "$2" ;;
* ) printf '%s\n' "$1/$2" ;;
esac
}
Finally, the function for canonicalizing a path:
canonicalize_path() {
if [ -d "$1" ]; then
_canonicalize_dir_path "$1"
else
_canonicalize_file_path "$1"
fi
}
_canonicalize_dir_path() {
(cd "$1" 2>/dev/null && pwd -P)
}
_canonicalize_file_path() {
local dir file
dir=$(dirname -- "$1")
file=$(basename -- "$1")
(cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}
quickstart source: stackoverflow
MIT