# 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 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"  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. • bash is the only option possible or a awk, sed, ... could be used also ? Sep 4 '15 at 9:51 • @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. Sep 4 '15 at 14:09 ## 1 Answer 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" != // ]

        [ -z "$p" ] && p=/  This form is shorter:  [ "$p" ] || p=/

[ -n "$skip78" ] || { { [ "$p" != '//' ] || [ -z "$alt" ]; } && { stripslashes "$p" p
[ -z "$p" ] && p=/ } }  • 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. Oct 16 '15 at 20:57
• But actually I just realized that : "\${p:=/}" is clearly the right way to do that bit. Oct 16 '15 at 20:58