參考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) }