更改文件名路径目录

I need to change filename paths relative to a given folder.
I am dealing with multi-user shared buckets and it would be nice for the user not to know the complete file path.

I have an example below but it seems a bit dirty.

package main

import (
    "fmt"
    "strings"
)

func main() {
    secretUrl := "allusers/user/home/path/to/file"
    separator := "home/"

    newUrl := strings.Split(secretUrl, separator)

    newUserUrl := separator + newUrl[len(newUrl)-1]
    fmt.Printf("%q
", newUserUrl)

}

Also, the new path needs to start from the separating point.

Is there another way to do this more elegantly? Examples would be appreciated.

I think a nice platform-independent solution is to use the golang path/filepath Split function. Does not make assumptions about what path separators look like, handles volumes, etc...

import (
    "path/filepath"
)

func subpath(homeDir, prevDir string) string {
    subFiles := ""
    for {
        dir, file := filepath.Split(prevDir)
        if len(subFiles) > 0 {
            subFiles = file + string(filepath.Separator) + subFiles
        } else {
            subFiles = file
        }
        if file == homeDir {
            break
        }
        if len(dir) == 0 || dir == prevDir {
            break
        }
        prevDir = dir[:len(dir) - 1]
    }
    return subFiles
}

Call with

subpath("home", "allusers/user/home/path/to/file")

To handle cases where "home" may appear more than once and you wish to match the first:

func subpath(homeDir, prevDir string) (subFiles string, found bool) {
    for {
        dir, file := filepath.Split(prevDir)
        if len(subFiles) > 0 {
            subFiles = file + string(filepath.Separator) + subFiles
        } else {
            subFiles = file
        }
        if len(dir) == 0 || dir == prevDir {
            return
        }
        prevDir = dir[:len(dir) - 1]
        if file == homeDir {
            found = true
            // look for it lower down
            lower, foundAgain := subpath(homeDir, prevDir)
            if foundAgain {
                subFiles = lower + string(filepath.Separator) + subFiles
            }
            return
        }
    }
}

Call with

path, found = subpath("home", "allusers/user/home/path/home2/home/to/file")
if found {
    fmt.Printf("%q
", path)
} else {
    fmt.Printf("not found
")
}

The rule of thumb is do not manipulate file paths as strings. There's too many edge cases. Go has the path and filepath libraries to manipulate paths.

But there are exceptions. Go's path and filepath libraries are limited in their functionality, like it lacks a way to split a path into an array or check if one file path is a child of another. Sometimes it's far more efficient to use string functions. You can avoid a lot of problems by first calling filepath.Clean on them to normalize them.

If the prefix is always allusers/user/home then you can split the paths into slices, after cleaning them, and compare them as arrays.

// Clean will take care of // and trailing /
secret := filepath.Clean("allusers//user/home//path/to/file")
base   := filepath.Clean("allusers/user/home/")

// Clean has made splitting on / safe.
secret_parts := strings.Split(secret, "/")
base_parts   := strings.Split(base, "/")

Now we have two arrays of all the pieces to compare. First, check to make sure that base_parts is really the prefix of secret_parts with reflect.DeepEqual on just the first few elements of secret_parts. The same number as in base_parts.

func sliceHasPrefix( s, prefix []string  ) bool {
    if len(s) < len(prefix) {
        return false
    }

    return reflect.DeepEqual(prefix, s[0:len(prefix)])
}

Then we can use filepath.Join to put the remaining parts back together.

if sliceHasPrefix( secret_parts, base_parts ) {
    fmt.Println( filepath.Join(secret_parts[len(base_parts):]...) )
} else {
    panic("secret is not inside base")
}