POSIX shell-only dirname replacement
For many years I have been using a snippet that worked well for all purposes I tried:
a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BINDIR=$(cd "$a"; pwd)
But today I realized it does not play well with /
, i.e. if the
script is placed in /script.sh
, the BINDIR
will contain current
directory (whatever it is) instead of /
.
TL;DR; the answer is
a="/$0"; a="${a%/*}"; a="${a:-.}"; a="${a##/}/"; BINDIR=$(cd "$a"; pwd)
UPDATE: Fixed also space handling reported at stackoverflow.
Tested with busybox sh
(1.26.2), loksh
(OpenBSD 6.1 ksh), mksh
(R55), as well as /bin/sh
from NetBSD 7.0.1_PATCH.
If you want to learn more read on.
In order to play with it properly, I wrote a test script:
#!/bin/sh
a="/$0"; a="${a%/*}"; a="${a:-.}"; a="${a##/}/"; BINDIR=$(cd "$a" || exit; pwd)
test "$1" = "-d" && DEBUG=1
debug() {
test -n "$DEBUG" && echo "$@" 1>&2
}
oldf() {
a="/$1"; a=${a%/*}; a=${a#/}; a=${a:-.}; BINDIR=$(cd "$a" || exit ; pwd)
echo "$BINDIR"
}
gdir() {
a="/$1";
debug "$a"
a="${a%/*}";
debug "$a"
a="${a:-.}";
debug "$a"
a="${a##/}";
debug "$a"
a="${a:-/}";
echo "$a"
}
# grdir = Get Real Directory
# works only on existing directories because it uses 'cd' in the last step
grdir() {
#a="/$1"; a=${a%/*}; a=${a#/}; a=${a:-.}; BINDIR=$(cd "$a"; pwd)
a=$(gdir "$1")
A=$(cd "$a" || exit; pwd)
echo "$A"
}
BINDIR=$(grdir "$0")
expectit() {
func=${3:-grdir}
A=$($func "$1")
if
test "$A" = "$2"
then
echo "OK: $func $1 ==> $2"
else
echo "FAILED: $func $1 !=> $A"
fi
}
expectit "/a.sh" "/"
expectit /usr/bin/a.sh /usr/bin
expectit ./a.sh "$PWD"
expectit a.sh "$PWD"
expectit /a.sh / gdir
expectit /made/up/a.sh /made/up gdir
expectit ./a.sh . gdir
expectit a.sh . gdir
expectit "/this is spaced/dir/a.sh" "/this is spaced/dir" gdir
# Here you can see what went wrong with the old one
echo expect failure in the next line
expectit /a.sh / oldf
expectit /usr/bin/a.sh /usr/bin oldf
expectit ./a.sh "$PWD" oldf
expectit a.sh "$PWD" oldf
Let it be in public domain.