#!/bin/sh # Sets REPLY to the absolute, well-formed path of a physical directory that # contains (or would contain) the supplied path. (Equivalent to the dirname of # realpath.resolved "$1".) Always succeeds. realpath_location(){ realpath_follow "$1"; realpath_absolute "$REPLY" ".."; } # Sets REPLY to the absolute, well-formed path of $1, with symlinks in the # final path portion resolved. (Equivalent to the realpath.absolute of # realpath.follow "$1".) Always succeeds. The result is not a symlink unless it # is inaccessible or part of a symlink cycle. realpath_resolved(){ realpath_follow "$1"; realpath_absolute "$REPLY"; } realpath_dirname() { REPLY=$(dirname "$1"); } realpath_basename() { REPLY=$(basename "$1"); } # Sets REPLY to the first non-symlink (or last accessible, non-looping symlink) # in the symlink chain beginning at $1. Replies with the unchanged $1 if it is # not a symlink. Always succeeds. The result is not a symlink unless it is # inaccessible or part of a symlink cycle. (Note: the result can be a relative # path, if both $1 and all the symlinks in the chain are relative. Use # realpath.resolved instead if you want a guaranteed-absolute path.) realpath_follow() { while [ -L "$1" ] && target=$(readlink -- "$1"); do realpath_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; do [ "$REPLY" = "$target" ] && break 2; done # Add to the loop-detect list and tail-recurse set -- "$REPLY" "$@" done REPLY="$1" } # Sets REPLY to the absolute, well-formed combination of the supplied path(s). # Always succeeds. # # Each path may be absolute or relative. The resulting path is the combination # of the last absolute path in the list supplied, combined with any relative # paths that follow it. If no absolute paths are given, the relative paths are # processed relative to $PWD -- so passing zero arguments simply returns $PWD. # # Relative path parts are resolved logically rather than physically. That is to # say, .. is processed by removing elements from the path string, rather than # by inspecting the filesystem. (So symlinks are not processed in any way, and # the existence or accessibility of the files and directories is irrelevant: # with the exception of defaulting to $PWD, the result is obtained solely via # string manipulation of the supplied paths.) # # As per POSIX, bash, Python, and other path handling libraries, absolute paths # beginning with exactly two slashes are treated specially. The string returned # by realpath.absolute will begin with two slashes if the last absolute path # argument began with exactly two slashes; otherwise the result will begin with # only one slash, no matter how many slashes the last absolute path argument # began with. realpath_absolute() { REPLY=$PWD while [ $# -gt 0 ]; do if echo "$1" | grep -E '^(//[^/]*)$' >/dev/null 2>&1; then REPLY=// first=$(echo "$1" | cut -c3-) shift set -- "$first" "$@" continue fi 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 ;; ..) realpath_dirname "$REPLY" shift ;; *) REPLY="${REPLY%/}/$1"; shift ;; esac done } # Sets REPLY to the fully canonicalized form of $1, recursively resolving # symlinks in every part of the path where that can be done, roughly equivalent # to realpath -m or readlink -m. Always succeeds, but potentially rather slow, # depending on how many directories are symlinks. # # You don't really need this function unless you are trying to determine # whether divergent paths lead to the "same" file. For use cases that don't # involve comparing paths, realpath.resolved should be sufficient, or perhaps # even realpath.absolute. (Note, too, that using canonical paths can result in # user confusion, since users then have to reconcile their inputs with your # outputs!) realpath_canonical() { realpath_follow "$1"; set -- "$REPLY" # $1 is now resolved realpath_basename "$1"; set -- "$1" "$REPLY" # $2 = basename $1 realpath_dirname "$1" [ "$REPLY" != "$1" ] && realpath_canonical "$REPLY"; # recurse unless root realpath_absolute "$REPLY" "$2"; # combine canon parent w/basename } # Sets REPLY to the shortest relative path from basedir to path. basedir # defaults to $PWD if not supplied. Always succeeds. # # The path and basedir are preprocessed with realpath.absolute, so they can be # relative paths, or absolute ones containing relative components. (The result # is identical to calling Python's os.path.relpath function with the same # arguments, but is much faster than even the fork to start Python would be.) # # The main use case for this function is portably creating relative symlinks # without needing ln --relative or realpath --relative-to. Specifically, if you # are doing ln -s somepath somedir/link, you can make it relative using # realpath.relative somepath somedir; ln -s "$REPLY" somedir/link. realpath_relative() { target="" realpath_absolute "$1" shift set -- "$REPLY" "$@" realpath_absolute "${2-$PWD}" X while realpath_dirname "$REPLY"; [ "$1" != "$REPLY" ] && [ "$1" = "${1#${REPLY%/}/}" ]; do target=../$target done [ "$1" = "$REPLY" ] && REPLY=${target%/} || REPLY="$target${1#${REPLY%/}/}" REPLY=${REPLY:-.} }