add realpath_m.sh as an all-in-one replacement for realpath -m

This commit is contained in:
Johannes 'josch' Schauer 2020-03-22 09:53:39 +01:00
parent b9c659a70a
commit dda942d7f1
Signed by: josch
GPG key ID: F2CBA5C78FBD83E1
4 changed files with 108 additions and 18 deletions

View file

@ -35,4 +35,4 @@ To verify that the script works with busybox:
$ mkdir busybox $ mkdir busybox
$ busybox --install -s busybox $ busybox --install -s busybox
$ PATH=$(pwd)/busybox ./test.sh $ PATH=$(pwd)/busybox /bin/busybox sh ./test.sh

View file

@ -1,4 +1,4 @@
#!/usr/bin/env sh #!/bin/sh
# Sets REPLY to the absolute, well-formed path of a physical directory that # 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 # contains (or would contain) the supplied path. (Equivalent to the dirname of

80
realpath_m.sh Executable file
View file

@ -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"

42
test.sh
View file

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
set -e set -eu
# we use the full path to realpath so that the coreutils version of realpath is # we use the full path to realpath so that the coreutils version of realpath is
# used even when run with busybox in $PATH # used even when run with busybox in $PATH
@ -8,6 +8,10 @@ realpath_m() {
REPLY=$(/usr/bin/realpath -m "$1") REPLY=$(/usr/bin/realpath -m "$1")
} }
realpath_m2() {
REPLY=$("$origpwd/realpath_m.sh" "$1")
}
assert() { assert() {
expected=$1 expected=$1
shift shift
@ -25,6 +29,7 @@ compare() {
assert "$("$cmd" "$@")" realpath_"$cmd" "$@" assert "$("$cmd" "$@")" realpath_"$cmd" "$@"
} }
origpwd=$(pwd)
numokay=0 numokay=0
numfail=0 numfail=0
@ -131,14 +136,14 @@ rm x y z; rmdir q
# Current directory: # 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" .
assert "$PWD" "$cmd" "$PWD" assert "$PWD" "$cmd" "$PWD"
done done
# Nonexistent file: # 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" x
assert "$PWD/x" "$cmd" "$PWD/x" assert "$PWD/x" "$cmd" "$PWD/x"
done done
@ -146,7 +151,7 @@ done
# Symlink to non-existent target: # Symlink to non-existent target:
ln -s y x 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" x
assert "$PWD/y" "$cmd" "$PWD/x" assert "$PWD/y" "$cmd" "$PWD/x"
done done
@ -155,7 +160,7 @@ done
# Symlink chain: # Symlink chain:
ln -s z y 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" x
assert "$PWD/z" "$cmd" "$PWD/x" assert "$PWD/z" "$cmd" "$PWD/x"
done done
@ -167,6 +172,7 @@ ln -s y z
# difference to realpath -m: returned path is the target # difference to realpath -m: returned path is the target
assert "$PWD/z" realpath_canonical x assert "$PWD/z" realpath_canonical x
assert "$PWD/y" realpath_m x assert "$PWD/y" realpath_m x
assert "$PWD/z" realpath_m2 x
rm x y z rm x y z
@ -177,19 +183,19 @@ mkdir z
ln -s $PWD/y x ln -s $PWD/y x
ln -s $PWD/x z/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" x
assert "$PWD/y" "$cmd" z/x assert "$PWD/y" "$cmd" z/x
done done
ln -sf ../x z/x 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" x
assert "$PWD/y" "$cmd" z/x assert "$PWD/y" "$cmd" z/x
done done
ln -s z q 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/z" "$cmd" q
assert "$PWD/y" "$cmd" q/../x assert "$PWD/y" "$cmd" q/../x
assert "$PWD/z/a" "$cmd" q/a assert "$PWD/z/a" "$cmd" q/a
@ -211,7 +217,7 @@ assert "$PWD/q/foo" realpath_location q/foo/bar
### Missing elements ### 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/baz" "$cmd" q/foo/bar/baz
assert "$PWD/z/foo/bar" "$cmd" q/foo/bar assert "$PWD/z/foo/bar" "$cmd" q/foo/bar
assert /non-existent "$cmd" /non-existent assert /non-existent "$cmd" /non-existent
@ -224,7 +230,7 @@ done
mkdir foo mkdir foo
cd 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/z" "$cmd" "$OLDPWD/q"
assert "$OLDPWD/y" "$cmd" "$OLDPWD/q/x" assert "$OLDPWD/y" "$cmd" "$OLDPWD/q/x"
assert "$OLDPWD/y" "$cmd" "../q/x" assert "$OLDPWD/y" "$cmd" "../q/x"
@ -234,15 +240,19 @@ done
# We can follow symlinks even if $PWD is inaccessible: # We can follow symlinks even if $PWD is inaccessible:
rmdir ../foo rmdir ../foo
assert "$OLDPWD/z" realpath_canonical "$OLDPWD/q" for cmd in realpath_canonical; do
assert "$OLDPWD/y" realpath_canonical "../q/x" 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: # And we can do non-symlink canonicalizing on the base dir of the results:
assert "$OLDPWD/z" realpath_canonical "../z" for cmd in realpath_canonical; do
assert "$OLDPWD/z/foo" realpath_canonical "../z/foo" assert "$OLDPWD/z" "$cmd" "../z"
assert "$OLDPWD/foo" realpath_canonical "../foo" assert "$OLDPWD/z/foo" "$cmd" "../z/foo"
assert "$OLDPWD/z" realpath_canonical "../q" assert "$OLDPWD/foo" "$cmd" "../foo"
assert "$OLDPWD/z" "$cmd" "../q"
done
cd .. cd ..