#!/bin/sh set -eu realpath_canonical() { # resolve symlink (if there is one) and use $@ to detect symlink loops while [ -L "$1" ] && target=$(readlink -- "$1"); do REPLY=$(dirname "$1") # Resolve relative to symlink's directory if [ "$REPLY" = "." ]; then REPLY=$target else case "$target" in /*) REPLY=$target;; *) REPLY=$REPLY/$target;; esac fi # Break out if we found a symlink loop for target in "$@"; do [ "$REPLY" = "$target" ] && break 2; done # Add to the loop-detect list and tail-recurse set -- "$REPLY" "$@" done # recurse unless root REPLY=$(dirname "$1") if [ "$REPLY" != "/" ] && [ "$REPLY" != "." ]; then REPLY=$(realpath_canonical "$REPLY"); fi # set $1 to dirname and $2 to basename set -- "$REPLY" "$(basename "$1")" # combine canon parent w/basename REPLY=$PWD while [ $# -gt 0 ]; do # handle case of component starting with double slash and no # other slashes separately because it cannot be solved by # globbing if echo "$1" | grep -E '^//[^/]*$' >/dev/null 2>&1; then REPLY=// first=$(echo "$1" | cut -c3-) shift set -- "$first" "$@" continue fi # all other cases can be expressed by globbing case $1 in /*) REPLY=/ first=$(echo "$1" | sed -e 's/^\/\+//') shift set -- "$first" "$@" ;; */*) front=${1%%/*} back=$(echo "$1" | sed -e "s/^$front\\/\\+//") shift set -- "$front" "$back" "$@" ;; ''|.) shift ;; ..) REPLY=$(dirname "$REPLY") shift ;; *) REPLY="${REPLY%/}/$1" shift ;; esac done echo "$REPLY" } if [ $# -ne 1 ]; then echo "usage: $0 path" exit 1 fi realpath_canonical "$1"