From dda942d7f1e9a2c2b01fd072f2c59fc29d5c5c9b Mon Sep 17 00:00:00 2001 From: Johannes 'josch' Schauer Date: Sun, 22 Mar 2020 09:53:39 +0100 Subject: [PATCH] add realpath_m.sh as an all-in-one replacement for realpath -m --- README.md | 2 +- realpath.sh | 2 +- realpath_m.sh | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++ test.sh | 42 ++++++++++++++++----------- 4 files changed, 108 insertions(+), 18 deletions(-) create mode 100755 realpath_m.sh diff --git a/README.md b/README.md index 7688247..dee1921 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,4 @@ To verify that the script works with busybox: $ mkdir busybox $ busybox --install -s busybox - $ PATH=$(pwd)/busybox ./test.sh + $ PATH=$(pwd)/busybox /bin/busybox sh ./test.sh diff --git a/realpath.sh b/realpath.sh index 855df84..157dcd1 100755 --- a/realpath.sh +++ b/realpath.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/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 diff --git a/realpath_m.sh b/realpath_m.sh new file mode 100755 index 0000000..5abdb72 --- /dev/null +++ b/realpath_m.sh @@ -0,0 +1,80 @@ +#!/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" diff --git a/test.sh b/test.sh index de6c589..b3b5ca3 100755 --- a/test.sh +++ b/test.sh @@ -1,6 +1,6 @@ #!/bin/sh -set -e +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 @@ -8,6 +8,10 @@ realpath_m() { REPLY=$(/usr/bin/realpath -m "$1") } +realpath_m2() { + REPLY=$("$origpwd/realpath_m.sh" "$1") +} + assert() { expected=$1 shift @@ -25,6 +29,7 @@ compare() { assert "$("$cmd" "$@")" realpath_"$cmd" "$@" } +origpwd=$(pwd) numokay=0 numfail=0 @@ -131,14 +136,14 @@ rm x y z; rmdir q # Current directory: -for cmd in realpath_canonical realpath_m; do +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; do +for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/x" "$cmd" x assert "$PWD/x" "$cmd" "$PWD/x" done @@ -146,7 +151,7 @@ done # Symlink to non-existent target: ln -s y x -for cmd in realpath_canonical realpath_m; do +for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/y" "$cmd" x assert "$PWD/y" "$cmd" "$PWD/x" done @@ -155,7 +160,7 @@ done # Symlink chain: ln -s z y -for cmd in realpath_canonical realpath_m; do +for cmd in realpath_canonical realpath_m realpath_m2; do assert "$PWD/z" "$cmd" x assert "$PWD/z" "$cmd" "$PWD/x" done @@ -167,6 +172,7 @@ 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 @@ -177,19 +183,19 @@ mkdir z ln -s $PWD/y x ln -s $PWD/x z/x -for cmd in realpath_canonical realpath_m; do +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; do +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; do +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 @@ -211,7 +217,7 @@ assert "$PWD/q/foo" realpath_location q/foo/bar ### Missing elements -for cmd in realpath_canonical realpath_m; do +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 @@ -224,7 +230,7 @@ done mkdir foo cd foo -for cmd in realpath_canonical realpath_m; do +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" @@ -234,15 +240,19 @@ done # We can follow symlinks even if $PWD is inaccessible: rmdir ../foo -assert "$OLDPWD/z" realpath_canonical "$OLDPWD/q" -assert "$OLDPWD/y" realpath_canonical "../q/x" +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: -assert "$OLDPWD/z" realpath_canonical "../z" -assert "$OLDPWD/z/foo" realpath_canonical "../z/foo" -assert "$OLDPWD/foo" realpath_canonical "../foo" -assert "$OLDPWD/z" realpath_canonical "../q" +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 ..