go标准库的学习-path/filepath
参考https://studygolang.com/pkgdoc
标准库path中有的该path/filepath库中都有,所以一般都使用path/filepath
导入方式:
import "path/filepath"
filepath包实现了兼容各操作系统的文件路径的实用操作函数。
1)constants常量
const ( Separator = os.PathSeparator //"/" ListSeparator = os.PathListSeparator //":" )
2)var变量
var ErrBadPattern = errors.New("syntax error in pattern")
ErrBadPattern表示一个glob模式匹配字符串的格式错误。
var SkipDir = errors.New("skip this directory")
用作WalkFunc类型的返回值,表示该次调用的path参数指定的目录应被跳过。本错误不应被任何其他函数返回。
3)函数
1》type WalkFunc
type WalkFunc func(path string, info os.FileInfo, err error) error
Walk函数对每一个文件/目录都会调用WalkFunc函数类型值。调用时path参数会包含Walk的root参数作为前缀;就是说,如果Walk函数的root为"dir",该目录下有文件"a",将会使用"dir/a"调用walkFn参数。walkFn参数被调用时的info参数是path指定的地址(文件/目录)的文件信息,类型为os.FileInfo。
如果遍历path指定的文件或目录时出现了问题,传入的参数err会描述该问题,WalkFunc类型函数可以决定如何去处理该错误(Walk函数将不会深入该目录);如果该函数返回一个错误,Walk函数的执行会中止;只有一个例外,如果Walk的walkFn返回值是SkipDir,将会跳过该目录的内容而Walk函数照常执行处理下一个文件。
2》func Walk
func Walk(root string, walkFn WalkFunc) error
Walk函数会遍历root指定的目录下的文件树,对每一个该文件树中的目录和文件都会调用walkFn,包括root自身。所有访问文件/目录时遇到的错误都会传递给walkFn过滤。文件是按词法顺序遍历的,这让输出更漂亮,但也导致处理非常大的目录时效率会降低。Walk函数不会遍历文件树中的符号链接(快捷方式)文件包含的路径。
3》源码
Walk()
func Walk(root string, walkFn WalkFunc) error { info, err := os.Lstat(root) //获取fileInfo if err != nil { err = walkFn(root, nil, err) //如果获取fileInfo的过程中出错了,则将错误err传入walkFn进行处理 } else { err = walk(root, info, walkFn)//如果成功得到了fileInfo,就将info作为参数,调用walk } if err == SkipDir {//如果输出的错误是SkipDir,那么就会跳过该目录的内容 return nil } return err }
walk()
func walk(path string, info os.FileInfo, walkFn WalkFunc) error { if !info.IsDir() { //如果info传入的是文件,则遍历下一个 return walkFn(path, info, nil) } names, err := readDirNames(path) //读取path下的所有目录和文件,并返回目录项的排序列表 err1 := walkFn(path, info, err) // If err != nil, walk can't walk into this directory. // err1 != nil means walkFn want walk to skip this directory or stop walking. // Therefore, if one of err and err1 isn't nil, walk will return. if err != nil || err1 != nil { // The caller's behavior is controlled by the return value, which is decided // by walkFn. walkFn may ignore err and return nil. // If walkFn returns SkipDir, it will be handled by the caller. // So walk should return whatever walkFn returns. return err1 } //遍历文件和目录列表 for _, name := range names { //连接得到目录或文件路径 filename := Join(path, name) //然后获取该文件或目录信息 fileInfo, err := lstat(filename) if err != nil { if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir { return err } } else { //递归 err = walk(filename, fileInfo, walkFn) if err != nil { //遍历文件发生错误或者目录发生错误且不能跳过,则返回err if !fileInfo.IsDir() || err != SkipDir { return err } } } } return nil }
有了他们,我们就能对指定目录下的目录和文件进行指定的操作walkFunc。
举个最简单的例子,如果想要遍历打印目录root下的文件名,则:
package main import( "fmt" "path/filepath" "os" ) func main() { filepath.Walk("/Users/user/go-learning", func(path string, info os.FileInfo, err error)error{ fmt.Printf("%s \n", path) return nil }) }
返回:
userdeMBP:go-learning user$ go run test.go /Users/user/go-learning /Users/user/go-learning/.DS_Store /Users/user/go-learning/hello.go /Users/user/go-learning/stacker /Users/user/go-learning/stacker/.DS_Store /Users/user/go-learning/stacker/stack /Users/user/go-learning/stacker/stack/stack.go /Users/user/go-learning/stacker/stacker.go /Users/user/go-learning/test.go /Users/user/go-learning/test.txt /Users/user/go-learning/testCreate.txt
4》func IsAbs
func IsAbs(path string) bool
IsAbs返回路径是否是一个绝对路径。
5》func Abs
func Abs(path string) (string, error)
Abs函数返回path代表的绝对路径,如果path不是绝对路径,会加入当前工作目录以使之成为绝对路径。因为硬链接的存在,不能保证返回的绝对路径是唯一指向该地址的绝对路径。
6》Rel
func Rel(basepath, targpath string) (string, error)
Rel函数返回一个相对路径,将basepath和该路径用路径分隔符连起来的新路径在词法上等价于targpath。也就是说,Join(basepath, Rel(basepath, targpath))等价于targpath本身。如果成功执行,返回值总是相对于basepath的,即使basepath和targpath没有共享的路径元素。如果两个参数一个是相对路径而另一个是绝对路径,或者targpath无法表示为相对于basepath的路径,将返回错误。
⚠️要求 targpath 和 basepath 必须“都是相对路径”或“都是绝对路径”。
举例说明:
package main import( "fmt" "path/filepath" "log" ) func main() { fmt.Println(filepath.IsAbs("/Users/user/go-learning/test.txt ")) //true fmt.Println(filepath.IsAbs("./test.txt ")) //false abs, _ := filepath.Abs("./test.txt ") fmt.Println(abs) ///Users/user/go-learning/test.txt paths := []string{ "/Users/user/go-learning/stacker", "/Users/user/go", "./stacker", } base := "/Users/user/go-learning" for _, target := range paths { rel, err := filepath.Rel(base, target) if err != nil { log.Fatal(err) } fmt.Printf("%q : %q %v\n", target, rel, err) } //返回: // "/Users/user/go-learning/stacker" : "stacker" <nil> // "/Users/user/go" : "../go" <nil> // 2019/01/24 09:32:57 Rel: can't make ./stacker relative to /Users/user/go-learning // exit status 1 }
上面可见当两者不都是绝对或相对路径时会报错并退出
如果将两个都写成相对路径,就成功了:
package main import( "fmt" "path/filepath" "log" ) func main() { target := "./stacker" base := "./" rel, err := filepath.Rel(base, target) if err != nil { log.Fatal(err) } fmt.Printf("%q : %q %v\n", target, rel, err) }
返回:
userdeMBP:go-learning user$ go run test.go "./stacker" : "stacker" <nil>
7》func SplitList
func SplitList(path string) []string
将PATH或GOPATH等环境变量里的多个路径分割开(这些路径被OS特定的表分隔符连接起来)。与strings.Split函数的不同之处是:对"",SplitList返回[]string{},而strings.Split返回[]string{""}。
8》func Split
func Split(path string) (dir, file string)
Split函数将路径从最后一个路径分隔符后面位置分隔为两个部分(dir和file)并返回。如果路径中没有路径分隔符,函数返回值dir会设为空字符串,file会设为path。两个返回值满足path == dir+file。
举例:
package main import( "fmt" "path/filepath" ) func main() { fmt.Println(filepath.SplitList("/Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/bin:/usr/local/bin:/Users/user/go/bin:/usr/local/mysql/bin:")) dir, file := filepath.Split("/Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/bin:/usr/local/bin:/Users/user/go/bin:/usr/local/mysql/bin:") fmt.Println(dir, file) fmt.Println(filepath.SplitList("")) dir, file = filepath.Split("") fmt.Println(dir, file) }
返回:
userdeMBP:go-learning user$ go run test.go [/Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/bin /usr/local/bin /Users/user/go/bin /usr/local/mysql/bin ] /Library/Java/JavaVirtualMachines/jdk-10.0.1.jdk/Contents/Home/bin:/usr/local/bin:/Users/user/go/bin:/usr/local/mysql/ bin: [] userdeMBP:go-learning user$
9》func Join
func Join(elem ...string) string
Join函数可以将任意数量的路径元素放入一个单一路径里,会根据需要添加路径分隔符。结果是经过简化的,所有的空字符串元素会被忽略。
举例:
package main import( "fmt" "path/filepath" ) func main() { fmt.Println(filepath.Join("Users", "user", "go-learning"))//Users/user/go-learning }
10》func FromSlash
func FromSlash(path string) string
FromSlash函数将path中的斜杠('/')替换为路径分隔符(Unix的为'/',所以结果没有变化)并返回替换结果,多个斜杠会替换为多个路径分隔符。
11》func ToSlash
func ToSlash(path string) string
ToSlash函数将path中的路径分隔符(Unix的为'/',所以结果没有变化)替换为斜杠('/')并返回替换结果,多个路径分隔符会替换为多个斜杠。
举例(因为我的系统是Unix,所以路径分隔符就是'/',所以结果并没有变化):
package main import( "fmt" "path/filepath" ) func main() { fromSlash := filepath.FromSlash("http://www.baidu.com/p") fmt.Println(fromSlash) fmt.Println(filepath.ToSlash("/usr/bin:/bin:/usr/sbin:/sbin")) }
返回:
userdeMBP:go-learning user$ go run test.go http://www.baidu.com/p /usr/bin:/bin:/usr/sbin:/sbin
12》func VolumeName
func VolumeName(path string) (v string)
VolumeName函数返回最前面的卷名。如Windows系统里提供参数"C:\foo\bar"会返回"C:";Unix/linux系统的"\\host\share\foo"会返回"\\host\share";其他平台会返回""。
13》func Dir
func Dir(path string) string
Dir返回路径除去最后一个路径元素的部分,即该路径最后一个元素所在的目录。在使用Split去掉最后一个元素后,会简化路径并去掉末尾的斜杠。如果路径是空字符串,会返回".";如果路径由1到多个路径分隔符后跟0到多个非路径分隔符字符组成,会返回单个路径分隔符;其他任何情况下都不会返回以路径分隔符结尾的路径。
14》func Base
func Base(path string) string
Base函数返回路径的最后一个元素。在提取元素前会求掉末尾的路径分隔符。如果路径是"",会返回".";如果路径是只有一个斜杆构成,会返回单个路径分隔符。
15》Ext
Ext函数返回path文件扩展名。返回值是路径最后一个路径元素的最后一个'.'起始的后缀(包括'.')。如果该元素没有'.'会返回空字符串。
举例:
package main import( "fmt" "path/filepath" ) func main() { fmt.Println(filepath.Dir("/Users/user/go-learning/test.go")) fmt.Println(filepath.Base("/Users/user/go-learning/test.go")) fmt.Println(filepath.Ext("/Users/user/go-learning/test.go")) dir, file := filepath.Split("/Users/user/go-learning/test.go") fmt.Printf("%q : %q\n", dir, file) }
返回:
userdeMBP:go-learning user$ go run test.go /Users/user/go-learning test.go .go "/Users/user/go-learning/" : "test.go"
可见Split()也能得到类似的效果,只是目录后还是会跟着一个路径分隔符'/'
16》func clean
func Clean(path string) string
Clean函数通过单纯的词法操作返回和path代表同一地址的最短路径。
它会不断的依次应用如下的规则,直到不能再进行任何处理:
1. 将连续的多个路径分隔符替换为单个路径分隔符 2. 剔除每一个.路径名元素(代表当前目录) 3. 剔除每一个路径内的..路径名元素(代表父目录)和它前面的非..路径名元素 4. 剔除开始一个根路径的..路径名元素,即将路径开始处的"/.."替换为"/"(假设路径分隔符是'/')
返回的路径只有其代表一个根地址时才以路径分隔符结尾,如Unix的"/"或Windows的`C:\`。
如果处理的结果是空字符串,Clean会返回"."。
举例:
package main import( "fmt" "path/filepath" ) func main() { paths := []string{ "/Users/user/go-learning/../go", "./stacker", "/Users/user/go-learning/../..", } for _, path := range paths{ fmt.Println(filepath.Clean(path)) } }
返回:
userdeMBP:go-learning user$ go run test.go /Users/user/go stacker /Users
所以其实使用简单的说法就是将一个复杂的路径变成其等价的最简单的表示形式
17》func EvalSymlinks
func EvalSymlinks(path string) (string, error)
EvalSymlinks函数返回path指向的符号链接(软链接)所包含的路径。如果path和返回值都是相对路径,会相对于当前目录;除非两个路径其中一个是绝对路径。
18》func Match
func Match(pattern, name string) (matched bool, err error)
如果name匹配shell文件命名模式pattern,则返回true。命名模式pattern为:
pattern: { term } term: '*' 匹配0或多个非路径分隔符的字符 '?' 匹配1个非路径分隔符的字符 '[' [ '^' ] { character-range } ']' 字符组(必须非空) c 匹配字符c(c != '*', '?', '\\', '[') '\\' c 匹配字符c character-range: c 匹配字符c(c != '\\', '-', ']') '\\' c 匹配字符c lo '-' hi 匹配区间[lo, hi]内的字符
Match要求匹配整个name字符串,而不是它的一部分。只有pattern语法错误时,会返回ErrBadPattern。
Windows系统中,不能进行转义:'\\'被视为路径分隔符。
举例:
package main import( "fmt" "path/filepath" ) func main() { fmt.Println(filepath.Match("*", "a")); //true <nil> fmt.Println(filepath.Match("*", "C:/a/b/c")); //false <nil> fmt.Println(filepath.Match("\\b", "b")); //true <nil> fmt.Println(filepath.Match("ab[c]", "abc")); //true <nil> fmt.Println(filepath.Match("[a-c]", "abc"));//false <nil>,false是因为[a-c]只匹配一个 fmt.Println(filepath.Match("[a-c][a-c][a-c]", "abc"));//true <nil>这样就对了 fmt.Println(filepath.Match("[^a-c]bc", "abc"));//false <nil> fmt.Println(filepath.Match("[^a-c]", "")); //false <nil> }
其他例子:
var matchTests = []MatchTest{ {"abc", "abc", true, nil}, {"*", "abc", true, nil}, {"*c", "abc", true, nil}, {"a*", "a", true, nil}, {"a*", "abc", true, nil}, {"a*", "ab/c", false, nil}, {"a*/b", "abc/b", true, nil}, {"a*/b", "a/c/b", false, nil}, {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, {"ab[c]", "abc", true, nil}, {"ab[b-d]", "abc", true, nil}, {"ab[e-g]", "abc", false, nil}, {"ab[^c]", "abc", false, nil}, {"ab[^b-d]", "abc", false, nil}, {"ab[^e-g]", "abc", true, nil}, {"a\\*b", "a*b", true, nil}, {"a\\*b", "ab", false, nil}, {"a?b", "a☺b", true, nil}, {"a[^a]b", "a☺b", true, nil}, {"a???b", "a☺b", false, nil}, {"a[^a][^a][^a]b", "a☺b", false, nil}, {"[a-ζ]*", "α", true, nil}, {"*[a-ζ]", "A", false, nil}, {"a?b", "a/b", false, nil}, {"a*b", "a/b", false, nil}, {"[\\]a]", "]", true, nil}, {"[\\-]", "-", true, nil}, {"[x\\-]", "x", true, nil}, {"[x\\-]", "-", true, nil}, {"[x\\-]", "z", false, nil}, {"[\\-x]", "x", true, nil}, {"[\\-x]", "-", true, nil}, {"[\\-x]", "a", false, nil}, {"[]a]", "]", false, ErrBadPattern}, {"[-]", "-", false, ErrBadPattern}, {"[x-]", "x", false, ErrBadPattern}, {"[x-]", "-", false, ErrBadPattern}, {"[x-]", "z", false, ErrBadPattern}, {"[-x]", "x", false, ErrBadPattern}, {"[-x]", "-", false, ErrBadPattern}, {"[-x]", "a", false, ErrBadPattern}, {"\\", "a", false, ErrBadPattern}, {"[a-b-c]", "a", false, ErrBadPattern}, {"[", "a", false, ErrBadPattern}, {"[^", "a", false, ErrBadPattern}, {"[^bc", "a", false, ErrBadPattern}, {"a[", "a", false, nil}, {"a[", "ab", false, ErrBadPattern}, {"*x", "xxx", true, nil}, }
19》func Glob
func Glob(pattern string) (matches []string, err error)
Glob函数返回所有匹配模式匹配字符串pattern的文件或者nil(如果没有匹配的文件)。pattern的语法和Match函数相同。pattern可以描述多层的名字,如/usr/*/bin/ed(假设路径分隔符是'/')。
举例:
package main import( "fmt" "path/filepath" "log" ) func main() { //返回指定文件下以test开头的文件 matches, err := filepath.Glob("/Users/user/go-learning/test*") //[/Users/user/go-learning/test.go /Users/user/go-learning/test.txt /Users/user/go-learning/testCreate.txt] if err != nil{ log.Fatal(err) } fmt.Println(matches) }