10
\$\begingroup\$

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
alt=$2
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" != '//' ] || [ -z "$alt" ]; } && {
        stripslashes "$p" p
        [ -z "$p" ] && p=/
    }
}

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

The POSIX spec for dirname is:

  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.

Update: Made a gist.

\$\endgroup\$
  • \$\begingroup\$ bash is the only option possible or a awk, sed, ... could be used also ? \$\endgroup\$ – NeronLeVelu Sep 4 '15 at 9:51
  • \$\begingroup\$ @NeronLeVelu The idea was something to replace the sub-shell needed for calling dirname. So anything that needs a sub-shell mostly negates the purpose. In theory this script would be turned into a function like stripslashes that operates on its arguments. \$\endgroup\$ – Etan Reisner Sep 4 '15 at 14:09
2
\$\begingroup\$

I don't think there's a really compelling reason for this eval in stripslashes:

eval "$2=\$i"

It's a dirty hack to update the parameter variable in-place. But this is not a common practice, I don't know other commands or built-ins that work that way, and I would find such usage unexpected. I would find it more usable if stripslashes behaved more like other commands do:

  • print the result on stdout
  • set the result in a designated variable (like REPLY)

That would be more in-line with common UNIX practices, and unsurprising, familiar.


As a minor nitpick, I avoid quoting when it's unnecessary. For example instead of [ "$p" != '//' ], I would write [ "$p" != // ]


Instead of this:

        [ -z "$p" ] && p=/

This form is shorter:

        [ "$p" ] || p=/

This might be subjective, but I would rewrite this part using regular if-else:

[ -n "$skip78" ] || {
    { [ "$p" != '//' ] || [ -z "$alt" ]; } && {
        stripslashes "$p" p
        [ -z "$p" ] && p=/
    }
}
\$\endgroup\$
  • \$\begingroup\$ We can't use stdout for stripslashes because that requires a sub-shell and at that point we are back to "just use dirname". We could just write into p directly by name but that makes stripslashes a one-use helper function as opposed to being generally useful written this way. With bash as opposed to sh I'd use printf -v there instead of eval. As to the quoting and the -z test those were to avoid possibly confusing people less familiar with the shell though [ -n "$p" ] || p=/ might be better for that purpose as I imagine -n is much more commonly used than -z. \$\endgroup\$ – Etan Reisner Oct 16 '15 at 20:57
  • 1
    \$\begingroup\$ But actually I just realized that : "${p:=/}" is clearly the right way to do that bit. \$\endgroup\$ – Etan Reisner Oct 16 '15 at 20:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.