path.Dir? filepath.Dir? 傻傻分不清楚

Go语言关于路径的操作有pathfilepath两个包。他们都可以处理路径相关操作,但你是否疑惑过这两个包该用哪个?它们有什么区别呢?小小的脑袋充满大大的疑惑呢。

疑惑.jpg

它们的区别其实看看源码就明白了,path.Dir源码在path/path.gofilepath.Dir源码在path/filepath/path.go

path/path.go文件开头有这样一段注释:

// Package path implements utility routines for manipulating slash-separated
// paths.
//
// The path package should only be used for paths separated by forward
// slashes, such as the paths in URLs. This package does not deal with
// Windows paths with drive letters or backslashes; to manipulate
// operating system paths, use the path/filepath package.

翻译过来就是说:path包提供了斜线分隔路径的常规操作。

但是,它只能用来处理正斜杠(/)分隔的路径。不能处理带有驱动符或反斜杠(\)的Windows路径,要操作不同操作系统的路径,请使用path/filepath包。


如何快速区分正斜杠和反斜杠呢?
\|/:往左偏的是反斜杠,往右偏的正斜杠。左反右正,右撇子表示很赞。


如果对 驱动符 感到疑惑,请看后面。

盲猜在path/filepath/path.go文件开头也有一段注释:

// Package filepath implements utility routines for manipulating filename paths
// in a way compatible with the target operating system-defined file paths.
//
// The filepath package uses either forward slashes or backslashes,
// depending on the operating system. To process paths such as URLs
// that always use forward slashes regardless of the operating
// system, see the path package.

翻译过来是:filepath包提供了兼容操作系统的路径操作。

它使用正斜杠还是反斜杠取决于操作系统。处理总是使用正斜杠的路径(如URL),请参见path包。

看完注释其实已经很清楚了,在处理路径时,应尽量使用filepath包,处理url时,使用path包。

为了呼应标题,我们来看看Dir函数的一些细节。

首先是path包的实现:

func Dir(path string) string {
	dir, _ := Split(path)
	return Clean(dir)
}

func Split(path string) (dir, file string) {
	i := strings.LastIndex(path, "/")
	return path[:i+1], path[i+1:]
}

path包的Dir函数简单粗暴,直接以最后一个/切分路径,然后清理。关于Clean函数,可以看看它的注释:

// Clean returns the shortest path name equivalent to path
// by purely lexical processing. It applies the following rules
// iteratively until no further processing can be done:
//
//	1. Replace multiple slashes with a single slash.
//	2. Eliminate each . path name element (the current directory).
//	3. Eliminate each inner .. path name element (the parent directory)
//	   along with the non-.. element that precedes it.
//	4. Eliminate .. elements that begin a rooted path:
//	   that is, replace "/.." by "/" at the beginning of a path.
//
// The returned path ends in a slash only if it is the root "/".
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,''
// https://9p.io/sys/doc/lexnames.html

Clean函数通过词法处理得到一个和原路径等价的最短路径。规则如下:

  1. 多个连续斜杠变成一个斜杠
  2. 消除.路径
  3. 消除内部的..以及(紧挨)..前面的(一个)路径
  4. 消除根路径(/)后的..,即(开头的)/..替换成/

只有根路径才会以斜杠结尾,换句话说Clean返回的路径会去掉末尾的斜杠。
如果参数是空字符串,Clean函数返回.

再来看看filepath包中Dir的实现:

func Dir(path string) string {
	vol := VolumeName(path)
	i := len(path) - 1 //记录最后一个分隔符下标
	for i >= len(vol) && !os.IsPathSeparator(path[i]) { //寻找最后一个分隔符
		i--
	}
	dir := Clean(path[len(vol) : i+1]) //路径清理
	if dir == "." && len(vol) > 2 {
		// must be UNC
		return vol
	}
	return vol + dir
}

这段逻辑也很简单:

  1. 首先获取路径的卷名
  2. 在卷名之后的路径中找到最后一个分隔符
  3. 对卷名和最后一个分隔符之间的路径做清理
  4. 拼接卷名和清理后的路径

此处Clean函数要比path包的稍微复杂,单基本规则还是一样的。

这里提到的卷名,包括前面的驱动符,是否让你感到疑惑呢?它们其实是同一个东西,看看VolumeName函数的实现就会明白了。

// VolumeName returns leading volume name.
// Given "C:\foo\bar" it returns "C:" on Windows.
// Given "\\host\share\foo" it returns "\\host\share".
// On other platforms it returns "".
func VolumeName(path string) string {
	return path[:volumeNameLen(path)]
}

// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func volumeNameLen(path string) int {
	if len(path) < 2 {
		return 0
	}
	// with drive letter
	c := path[0]
	if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
		return 2
	}
	// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
	if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
		!isSlash(path[2]) && path[2] != '.' {
		// first, leading `\\` and next shouldn't be `\`. its server name.
		for n := 3; n < l-1; n++ {
			// second, next '\' shouldn't be repeated.
			if isSlash(path[n]) {
				n++
				// third, following something characters. its share name.
				if !isSlash(path[n]) {
					if path[n] == '.' {
						break
					}
					for ; n < l; n++ {
						if isSlash(path[n]) {
							break
						}
					}
					return n
				}
				break
			}
		}
	}
	return 0
}

在Windows上其实就是你的盘符,比如C:VolumeName通过卷名长度来切片路径得到卷名,卷名长度通过volumeNameLen函数获取,在Windows上就是2。

这里涉及到一个东西叫做通用命名规范,英文名叫UNC (Universal Naming Convention)。这是一个通用的目录命名规范,我们没搜到规范原文,基本上你见到的目录都是遵循这个规范的,感兴趣的可以去搜索,各显神通吧。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值