确定路径是否在Go中的另一个路径内部

huangapple go评论161阅读模式
英文:

Determine if path is inside another path in Go

问题

我想删除文件的所有路径组件,直到(但不包括)整体基本目录。

示例:
/overall/basedir/a/b/c/file

我想删除"file",然后删除"c","b",然后如果可能的话删除"a"(目录不为空)。我想删除"basedir"或"overall"。

filepath.HasPrefix似乎是一个不错的选择,但它显然已被弃用:https://golang.org/pkg/path/filepath/#HasPrefix

我现在的代码是:

p := THEPATH

// 尝试删除文件和所有父目录,直到basedir
// FIXME: HasPrefix显然不好..有更好的方法吗?
for filepath.HasPrefix(p, baseDir) {
	err := os.Remove(p)
	if err != nil {
		break
	}
	// 向上爬一级
	p = filepath.Dir(p)
}

寻找一种简洁可靠的方法,适用于所有Go支持的平台。

英文:

I would like to delete all path components for a file up to (but not including) an overall base directory.

Example:
/overall/basedir/a/b/c/file

I want to remove "file" and then remove "c", "b" and then "a" if possible (directories not empty). I do not want to unlink "basedir" or "overall".

filepath.HasPrefix would seem to be a good option but it's apparently deprecated: https://golang.org/pkg/path/filepath/#HasPrefix

What I have now is:

p := THEPATH

// attempt to remove file and all parent directories up to the basedir
// FIXME: HasPrefix is apparently bad.. a better idea?
for filepath.HasPrefix(p, baseDir) {
	err := os.Remove(p)
	if err != nil {
		break
	}
	// climb up one
	p = filepath.Dir(p)
}

Looking for a succinct and reliable way that works on all Go supported platforms.

答案1

得分: 1

在我看来,如果你想要支持golang支持的所有平台,路径处理会相当复杂。下面是我迄今为止实现的解决方案(可能不是最简单的解决方案)。注意:

  1. 它支持通用的操作,而不仅仅是os.Remove
  2. 使用函数os.SameFile而不是基于字符串的路径比较,用于测试两个文件/目录是否相等。
  3. 在实现中,首先访问并将所有候选路径添加到visitedPaths切片中。然后,如果没有错误发生,对每个候选路径执行一个action

以下是代码:

package pathwalker

import (
    "os"
    "path/filepath"
    "strings"
)

type PathAction func(PathInfo) error
type PathInfo struct {
    FileInfo os.FileInfo
    FullPath string
}
type PathWalker struct {
    pathName     string
    basePath     string
    visitedPaths []PathInfo
    lastFi       os.FileInfo
}

//NewPathWalker creates PathWalker instance
func NewPathWalker(pathName, basePath string) *PathWalker {
    return &PathWalker{
        pathName: pathName,
        basePath: basePath,
    }
}

func (w *PathWalker) visit() (bool, error) {
    //Make sure path ends with separator
    basePath := filepath.Clean(w.basePath + string(filepath.Separator))
    baseInfo, err := os.Lstat(basePath)
    if err != nil {
        return false, err
    }

    //clean path name
    fi, err := os.Lstat(w.pathName)
    if err != nil {
        return false, err
    } else if fi.IsDir() {
        //When pathname is a directory, remove latest separator
        sep := string(filepath.Separator)
        cleanPath := filepath.Clean(w.pathName + sep)
        w.pathName = strings.TrimRight(cleanPath, sep)
    } else {
        w.pathName = filepath.Clean(w.pathName)
    }
    return w.doVisit(w.pathName, baseInfo)
}

//visit path recursively
func (w *PathWalker) doVisit(pathName string, baseInfo os.FileInfo) (bool, error) {
    //Get file info
    fi, err := os.Lstat(pathName)
    if err != nil {
        return false, err
    }

    //Stop when basePath equal to pathName
    if os.SameFile(fi, baseInfo) {
        return true, nil
    }

    //Top directory reached, but does not match baseInfo
    if w.lastFi != nil && os.SameFile(w.lastFi, fi) {
        return false, nil
    }
    w.lastFi = fi

    //Append to visited path list
    w.visitedPaths = append(w.visitedPaths, PathInfo{fi, pathName})

    //Move to upper path
    up := filepath.Dir(pathName)
    if up == "." {
        return false, nil
    }

    //Visit upper directory
    return w.doVisit(up, baseInfo)
}

//Walk perform action then return number of proceed paths and error
func (w *PathWalker) Walk(act PathAction) (int, error) {
    n := 0
    ok, err := w.visit()
    if err != nil {
        return 0, err
    } else if ok && act != nil {
        for _, pi := range w.visitedPaths {
            err := act(pi)
            if err != nil {
                return n, err
            }
            n++
        }
    }
    return n, nil
}

//VisitedPaths return list of visited paths
func (w *PathWalker) VisitedPaths() []PathInfo {
    return w.visitedPaths
}

然后,如果你想要删除basePath下的文件和父目录,可以这样做:

func remove(pathName, basePath string) {
    act := func(p pathwalker.PathInfo) error {
        if p.FileInfo.IsDir() {
            fmt.Printf("  Removing directory=%s\n", p.FullPath)
            return os.Remove(p.FullPath)
        }

        fmt.Printf("  Removing file=%s\n", p.FullPath)
        return os.Remove(p.FullPath)
    }

    pw := pathwalker.NewPathWalker(pathName, basePath)
    n, err := pw.Walk(act)
    fmt.Printf("Removed: %d/%d, err=%v\n", n, len(pw.VisitedPaths()), err)
}

如果你只想测试一个路径是否在另一个路径内,可以这样做:

n, err := pathwalker.NewPathWalker(fileName, basePath).Walk(nil)
if n > 0 && err != nil {
    //is inside another path
}
英文:

IMHO, path handling is rather complicated if you want to support all platforms that is supported by golang. Bellow is the solution that I've implemented so far (probably not the simplest one). Notes:

  1. It supports generalized action rather than only os.Remove
  2. Instead of string-based path comparison, function os.SameFile is used to test whether two files/directories are equal.
  3. In the implementation, at first all candidate paths are visited and added to visitedPaths slice. Then, if no error occurs, an action is perform to each candidate path.

The code:

package pathwalker
import (
"os"
"path/filepath"
"strings"
)
type PathAction func(PathInfo) error
type PathInfo struct {
FileInfo os.FileInfo
FullPath string
}
type PathWalker struct {
pathName     string
basePath     string
visitedPaths []PathInfo
lastFi       os.FileInfo
}
//NewPathWalker creates PathWalker instance
func NewPathWalker(pathName, basePath string) *PathWalker {
return &PathWalker{
pathName: pathName,
basePath: basePath,
}
}
func (w *PathWalker) visit() (bool, error) {
//Make sure path ends with separator
basePath := filepath.Clean(w.basePath + string(filepath.Separator))
baseInfo, err := os.Lstat(basePath)
if err != nil {
return false, err
}
//clean path name
fi, err := os.Lstat(w.pathName)
if err != nil {
return false, err
} else if fi.IsDir() {
//When pathname is a directory, remove latest separator
sep := string(filepath.Separator)
cleanPath := filepath.Clean(w.pathName + sep)
w.pathName = strings.TrimRight(cleanPath, sep)
} else {
w.pathName = filepath.Clean(w.pathName)
}
return w.doVisit(w.pathName, baseInfo)
}
//visit path recursively
func (w *PathWalker) doVisit(pathName string, baseInfo os.FileInfo) (bool, error) {
//Get file info
fi, err := os.Lstat(pathName)
if err != nil {
return false, err
}
//Stop when basePath equal to pathName
if os.SameFile(fi, baseInfo) {
return true, nil
}
//Top directory reached, but does not match baseInfo
if w.lastFi != nil && os.SameFile(w.lastFi, fi) {
return false, nil
}
w.lastFi = fi
//Append to visited path list
w.visitedPaths = append(w.visitedPaths, PathInfo{fi, pathName})
//Move to upper path
up := filepath.Dir(pathName)
if up == "." {
return false, nil
}
//Visit upper directory
return w.doVisit(up, baseInfo)
}
//Walk perform action then return number of proceed paths and error
func (w *PathWalker) Walk(act PathAction) (int, error) {
n := 0
ok, err := w.visit()
if err != nil {
return 0, err
} else if ok && act != nil {
for _, pi := range w.visitedPaths {
err := act(pi)
if err != nil {
return n, err
}
n++
}
}
return n, nil
}
//VisitedPaths return list of visited paths
func (w *PathWalker) VisitedPaths() []PathInfo {
return w.visitedPaths
}

Then if you want to remove file and parent directory under basePath, you can do:

func remove(pathName, basePath string) {
act := func(p pathwalker.PathInfo) error {
if p.FileInfo.IsDir() {
fmt.Printf("  Removing directory=%s\n", p.FullPath)
return os.Remove(p.FullPath)
}
fmt.Printf("  Removing file=%s\n", p.FullPath)
return os.Remove(p.FullPath)
}
pw := pathwalker.NewPathWalker(pathName, basePath)
n, err := pw.Walk(act)
fmt.Printf("Removed: %d/%d, err=%v\n", n, len(pw.VisitedPaths()), err)
}

If you just want to test whether a path is inside another path, you can do:

n, err := pathwalker.NewPathWalker(fileName, basePath).Walk(nil)
if n > 0 && err != nil {
//is inside another path
}

huangapple
  • 本文由 发表于 2017年6月7日 16:07:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/44407006.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定