initial commit

This commit is contained in:
Johannes 'josch' Schauer 2020-03-21 20:16:29 +01:00
commit b9c659a70a
Signed by: josch
GPG key ID: F2CBA5C78FBD83E1
4 changed files with 555 additions and 0 deletions

117
LICENSE Normal file
View file

@ -0,0 +1,117 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

38
README.md Normal file
View file

@ -0,0 +1,38 @@
This is an implementation of `realpath -m` (also known as `realpath
--canonicalize-missing`) for systems without coreutils. The differences to
`realpath -m` are:
- different return value when a symlink loop is detected
- also works if $PWD doesn't exist
The implementation is not POSIX compatible. Namely, POSIX does not specify the
`readlink` utility used by this script. Instead, the script takes the pragmatic
approach and just requires:
- a POSIX compliant shell (does not require bash), busybox sh will work
- dirname and basename
- sed
- grep -E
- readlink (without options)
As a result, the script works well under busybox.
The script is a port of [a bash script](https://github.com/bashup/realpaths)
written by PJ Eby and released in the public domain. Likewise, the author of
this script has waived all copyright on it as well. Differences of this
implementation to the one in bash:
- much slower due to excessive forking
- tests are pure shell without cram and without .devkit
- functions are named realpath_foo instead of realpath.foo because a dot is
not allowed in POSIX shell
To run the tests:
$ ./test.sh
To verify that the script works with busybox:
$ mkdir busybox
$ busybox --install -s busybox
$ PATH=$(pwd)/busybox ./test.sh

143
realpath.sh Executable file
View file

@ -0,0 +1,143 @@
#!/usr/bin/env 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:-.}
}

257
test.sh Executable file
View file

@ -0,0 +1,257 @@
#!/bin/sh
set -e
# 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")
}
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" "$@"
}
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; do
assert "$PWD" "$cmd" .
assert "$PWD" "$cmd" "$PWD"
done
# Nonexistent file:
for cmd in realpath_canonical realpath_m; 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; 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; 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
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; 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
assert "$PWD/y" "$cmd" x
assert "$PWD/y" "$cmd" z/x
done
ln -s z q
for cmd in realpath_canonical realpath_m; 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; 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; 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
assert "$OLDPWD/z" realpath_canonical "$OLDPWD/q"
assert "$OLDPWD/y" realpath_canonical "../q/x"
# 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"
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