2

I am trying to write the dirname function in Bash so that it doesn't use any external commands.

function dirname() {
  local path=$1
  [[ $path =~ ^[^/]+$ ]] && dir=. || {              # if path has no slashes, set dir to .
    [[ $path =~ ^/+$ ]]  && dir=/ || {              # if path has only slashes, set dir to /
      local IFS=/ dir_a i
      read -ra dir_a <<< "$path"                    # read the components of path into an array
      dir="${dir_a[0]}"
      for ((i=1; i < ${#dir_a[@]}; i++)); do        # strip out any repeating slashes
        [[ ${dir_a[i]} ]] && dir="$dir/${dir_a[i]}" # append unless it is an empty element
      done
    }
  }

  [[ $dir ]] && printf '%s\n' "$dir"                # print only if not empty
}

In order to remove any repeating / from the path, I had to use the array logic. Is there a simpler way to do the same with Bash Parameter Expansion? I tried, but I don't seem to get it right.

Basically, I want to replace all occurrences of multiple consecutive slashes with a single slash each.

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • `echo "${path%\/*}"`? – Cyrus Oct 18 '17 at 04:49
  • That would remove only the last `/` onwards. For example: if path is `a//b//c///d`, it would give us `a//b//c//`. – codeforester Oct 18 '17 at 04:55
  • 1
    Just out of curiosity, where do you find a path like `a//b//c///d`? The only thing I've run across close to that is converting windows path components with `drive:\\a\\b\\c\\file.ext` to POSIX `drive:/a/b/c/file.ext`. Where can you create a path like `a//b//c///d`? windoze? Or, is this just a generic *strip multiple `'//'`* exercise? – David C. Rankin Oct 18 '17 at 05:54
  • I am trying to mimic the dirname command. – codeforester Oct 18 '17 at 05:56
  • 1
    `$ dirname "//a///b////c/d//e.txt"` yields `"//a///b////c/d"`. Where does the stripping multiple slashes come from? (or am I just so thick I'm still missing it?) – David C. Rankin Oct 18 '17 at 06:00
  • @DavidC.Rankin - you are right. The `dirname` command doesn't strip those slashes. Not sure why I thought so. – codeforester Oct 19 '17 at 04:47
  • Similar question: [How to replace multiple spaces with a single space in a string with only bash built-ins](https://stackoverflow.com/q/50259869/6862601). – codeforester May 09 '18 at 20:11

1 Answers1

4

If extglob is on:

shopt -s extglob

you can do this:

printf '%s\n' "${path//\/+(\/)/\/}"

This uses the ${var//pattern/replacement} syntax to do a global substitution.

The pattern is \/+(\/) (with escaped slashes because / is the delimiter), which is really /+(/) (where +(/) means "one or more slashes").

melpomene
  • 84,125
  • 8
  • 85
  • 148