#!/bin/sh set -eu # we use the full path to realpath so that the coreutils version of realpath is # used even when run with busybox in $PATH realpath_m() { REPLY=$(/usr/bin/realpath -m "$1") } realpath_m2() { REPLY=$("$origpwd/realpath_m.sh" "$1") } assert() { expected=$1 shift if "$@" && [ "$REPLY" = "$expected" ]; then numok=$((numok+1)) else numfail=$((numfail+1)) echo "$@: expected '$expected', got '$REPLY'" fi } compare() { cmd=$1 shift assert "$("$cmd" "$@")" realpath_"$cmd" "$@" } origpwd=$(pwd) numokay=0 numfail=0 # realpath_dirname and realpath_basename should produce the same results as their operating system counterparts. . "./realpath.sh" for p1 in '' / // . ..; do for p2 in '' /; do for p3 in foo . ..; do for p4 in '' /bar /. /..; do for p5 in '' / // /// /. /..; do compare basename "$p1$p2$p3$p4$p5" compare dirname "$p1$p2$p3$p4$p5" done done done done done ## realpath_absolute # Outputs PWD with no args, joins relative args to PWD, removes empty parts, and # canonicalizes ./..: assert "$PWD" realpath_absolute assert "$PWD" realpath_absolute . assert "$PWD/x" realpath_absolute x assert "$PWD/x/y" realpath_absolute x y assert "$PWD/x/y" realpath_absolute x/z ../y assert "$PWD/x/y/z" realpath_absolute x y//z assert "$PWD/x/y/z" realpath_absolute x y '' z assert "$PWD/x/y/z" realpath_absolute x y '' z/. assert "$PWD/x/y/z" realpath_absolute x y '' z/. q .. r .. .. z # Ignores arguments to the left of an absolute path: assert /etc realpath_absolute /etc assert /etc/z/xq realpath_absolute x y z /etc/z q/../xq # Preserves double (but not triple+) slashes at the beginning of an absolute # path: assert //server/share/blah realpath_absolute //server share blah/ assert /server/share/blah realpath_absolute ///server/share blah assert /server/share/blah realpath_absolute ////server/share blah ## realpath_relative path dir # Outputs the relative path from dir to path: assert y realpath_relative y . assert y realpath_relative x/y x assert ../y realpath_relative y x assert ../y/z realpath_relative a/y/z a/x assert ../../y realpath_relative y a/x assert ../../../y realpath_relative y a/b/x # Avoids redundancy for common path elements: assert . realpath_relative y y assert .. realpath_relative y y/x assert ../.. realpath_relative y y/z/x assert ../../.. realpath_relative /y /y/z/x/a # Produces the same outputs as python's os.path.relpath: relative() { /usr/bin/python3 -c 'import sys,os.path; sys.stdout.write(os.path.relpath(*sys.argv[1:3]))' "$@"; } compare relative /q /q/r/s compare relative /q/r /q/r/s compare relative /q/r/s /q/r/s compare relative /q/r/s/t /q/r/s compare relative /q/r/s/t/u /q/r/s compare relative /q/r/t /q/r/s compare relative /q/r/t/u /q/r/s compare relative /q/t /q/r/s compare relative /q/t/u /q/r/s compare relative /t/u/v /q/r/s ## realpath_follow # Returns first non-symlink: assert "x" realpath_follow x ln -s y x assert "y" realpath_follow x ln -s z y assert "z" realpath_follow x # or last symlink before recursion sets in: ln -s x z assert "z" realpath_follow x assert "y" realpath_follow z assert "x" realpath_follow y # and relative-pathed symlinks are normalized to the symlink directory mkdir q assert "q/../z" realpath_follow q/../x rm x y z; rmdir q ## realpath_canonical # Current directory: for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD" "$cmd" . assert "$PWD" "$cmd" "$PWD" done # Nonexistent file: for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/x" "$cmd" x assert "$PWD/x" "$cmd" "$PWD/x" done # Symlink to non-existent target: ln -s y x for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/y" "$cmd" x assert "$PWD/y" "$cmd" "$PWD/x" done # Symlink chain: ln -s z y for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/z" "$cmd" x assert "$PWD/z" "$cmd" "$PWD/x" done # Symlink loop breaks on the looper: ln -s y z # difference to realpath -m: returned path is the target assert "$PWD/z" realpath_canonical x assert "$PWD/y" realpath_m x assert "$PWD/z" realpath_m2 x rm x y z # Absolute links and directories: touch y mkdir z ln -s $PWD/y x ln -s $PWD/x z/x for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/y" "$cmd" x assert "$PWD/y" "$cmd" z/x done ln -sf ../x z/x for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/y" "$cmd" x assert "$PWD/y" "$cmd" z/x done ln -s z q for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/z" "$cmd" q assert "$PWD/y" "$cmd" q/../x assert "$PWD/z/a" "$cmd" q/a done ## realpath_location # Returns the absolute (but not canonical) location of the directory physically # containing its target. Is equivalent to `realpath_absolute` if target isn't # a symlink: assert "$PWD" realpath_location x assert "$PWD" realpath_location z assert "$PWD/z" realpath_location z/a assert "$PWD" realpath_location q/x # -> ../x assert "$PWD/q" realpath_location q/foo assert "$PWD" realpath_location q/../foo assert "$PWD/q/foo" realpath_location q/foo/bar ### Missing elements for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/z/foo/bar/baz" "$cmd" q/foo/bar/baz assert "$PWD/z/foo/bar" "$cmd" q/foo/bar assert /non-existent "$cmd" /non-existent assert /etc/non-existent "$cmd" /etc/non-existent done ### Subdirectories, accessible and inaccessible # Works while the directory is accesible: mkdir foo cd foo for cmd in realpath_canonical realpath_m realpath_m2; do assert "$OLDPWD/z" "$cmd" "$OLDPWD/q" assert "$OLDPWD/y" "$cmd" "$OLDPWD/q/x" assert "$OLDPWD/y" "$cmd" "../q/x" done # realpath -m cannot operate on inaccessible $PWD # We can follow symlinks even if $PWD is inaccessible: rmdir ../foo for cmd in realpath_canonical; do assert "$OLDPWD/z" "$cmd" "$OLDPWD/q" assert "$OLDPWD/y" "$cmd" "../q/x" done # And we can do non-symlink canonicalizing on the base dir of the results: for cmd in realpath_canonical; do assert "$OLDPWD/z" "$cmd" "../z" assert "$OLDPWD/z/foo" "$cmd" "../z/foo" assert "$OLDPWD/foo" "$cmd" "../foo" assert "$OLDPWD/z" "$cmd" "../q" done cd .. rm x y q z/x rmdir z echo "number of successes: $numok" echo "number of failures: $numfail" if [ "$numfail" -gt 0 ]; then exit 1 fi