Skip to main content
1 of 6

POSIX shell compatible and POSIX conforming dirname implementation

The discussion in the comments of this answer made me wonder how hard it would be to write a built-in only version of dirname.

This is what I ended up with but I'm reasonably confident it should be able to be improved (I haven't spent much time thinking about it yet).

Any improvements/suggestions are welcome.

#!/bin/sh

p=$1
skip78=

stripslashes() {
    i=$1
    while [ "$i" != "$o" ]; do
        o=$i
        i=${i%/}
    done
    eval "$2=\$i"
}

[ "$p" = '//' ] || {
    case "$p" in
        *[!/]*|'')
            stripslashes "$p" p
            case "$p" in
                */*)
                    p=${p%/*}
                    ;;
                *)
                    p=.
                    skip78=skip78
                    ;;
            esac
            ;;
        *)
            p=/
            skip78=skip78
            ;;
    esac
}
[ -n "$skip78" ] || {
    { [ "$p" != '//' ] || [ -n "$2" ]; } && {
        stripslashes "$p" p
        [ -z "$p" ] && p=/
    }
}

printf -- %s\\n "$p"

The POSIX spec for dirname is (from [here])(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html):

  1. If string is //, skip steps 2 to 5.

  2. If string consists entirely of <slash> characters, string shall be set to a single <slash> character. In this case, skip steps 3 to 8.

  3. If there are any trailing <slash> characters in string, they shall be removed.

  4. If there are no <slash> characters remaining in string, string shall be set to a single <period> character. In this case, skip steps 5 to 8.

  5. If there are any trailing non- <slash> characters in string, they shall be removed.

  6. If the remaining string is //, it is implementation-defined whether steps 7 and 8 are skipped or processed.

  7. If there are any trailing <slash> characters in string, they shall be removed.

  8. If the remaining string is empty, string shall be set to a single <slash> character.