Source File
path.go
Belonging Package
path/filepath
// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// 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.package filepathimport ()// A lazybuf is a lazily constructed path buffer.// It supports append, reading previously appended bytes,// and retrieving the final string. It does not allocate a buffer// to hold the output until that output diverges from s.type lazybuf struct {path stringbuf []bytew intvolAndPath stringvolLen int}func ( *lazybuf) ( int) byte {if .buf != nil {return .buf[]}return .path[]}func ( *lazybuf) ( byte) {if .buf == nil {if .w < len(.path) && .path[.w] == {.w++return}.buf = make([]byte, len(.path))copy(.buf, .path[:.w])}.buf[.w] =.w++}func ( *lazybuf) () string {if .buf == nil {return .volAndPath[:.volLen+.w]}return .volAndPath[:.volLen] + string(.buf[:.w])}const (Separator = os.PathSeparatorListSeparator = os.PathListSeparator)// 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 Separator elements with a single one.// 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,// assuming Separator is '/'.//// The returned path ends in a slash only if it represents a root directory,// such as "/" on Unix or `C:\` on Windows.//// Finally, any occurrences of slash are replaced by Separator.//// 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.htmlfunc ( string) string {:=:= volumeNameLen()= [:]if == "" {if > 1 && [1] != ':' {// should be UNCreturn FromSlash()}return + "."}:= os.IsPathSeparator([0])// Invariants:// reading from path; r is index of next byte to process.// writing to buf; w is index of next byte to write.// dotdot is index in buf where .. must stop, either because// it is the leading slash or it is a leading ../../.. prefix.:= len():= lazybuf{path: , volAndPath: , volLen: }, := 0, 0if {.append(Separator), = 1, 1}for < {switch {case os.IsPathSeparator([]):// empty path element++case [] == '.' && (+1 == || os.IsPathSeparator([+1])):// . element++case [] == '.' && [+1] == '.' && (+2 == || os.IsPathSeparator([+2])):// .. element: remove to last separator+= 2switch {case .w > :// can backtrack.w--for .w > && !os.IsPathSeparator(.index(.w)) {.w--}case !:// cannot backtrack, but not rooted, so append .. element.if .w > 0 {.append(Separator)}.append('.').append('.')= .w}default:// real path element.// add slash if neededif && .w != 1 || ! && .w != 0 {.append(Separator)}// copy elementfor ; < && !os.IsPathSeparator([]); ++ {.append([])}}}// Turn empty string into "."if .w == 0 {.append('.')}return FromSlash(.string())}// ToSlash returns the result of replacing each separator character// in path with a slash ('/') character. Multiple separators are// replaced by multiple slashes.func ( string) string {if Separator == '/' {return}return strings.ReplaceAll(, string(Separator), "/")}// FromSlash returns the result of replacing each slash ('/') character// in path with a separator character. Multiple slashes are replaced// by multiple separators.func ( string) string {if Separator == '/' {return}return strings.ReplaceAll(, "/", string(Separator))}// SplitList splits a list of paths joined by the OS-specific ListSeparator,// usually found in PATH or GOPATH environment variables.// Unlike strings.Split, SplitList returns an empty slice when passed an empty// string.func ( string) []string {return splitList()}// Split splits path immediately following the final Separator,// separating it into a directory and file name component.// If there is no Separator in path, Split returns an empty dir// and file set to path.// The returned values have the property that path = dir+file.func ( string) (, string) {:= VolumeName():= len() - 1for >= len() && !os.IsPathSeparator([]) {--}return [:+1], [+1:]}// Join joins any number of path elements into a single path,// separating them with an OS specific Separator. Empty elements// are ignored. The result is Cleaned. However, if the argument// list is empty or all its elements are empty, Join returns// an empty string.// On Windows, the result will only be a UNC path if the first// non-empty element is a UNC path.func ( ...string) string {return join()}// Ext returns the file name extension used by path.// The extension is the suffix beginning at the final dot// in the final element of path; it is empty if there is// no dot.func ( string) string {for := len() - 1; >= 0 && !os.IsPathSeparator([]); -- {if [] == '.' {return [:]}}return ""}// EvalSymlinks returns the path name after the evaluation of any symbolic// links.// If path is relative the result will be relative to the current directory,// unless one of the components is an absolute symbolic link.// EvalSymlinks calls Clean on the result.func ( string) (string, error) {return evalSymlinks()}// Abs returns an absolute representation of path.// If the path is not absolute it will be joined with the current// working directory to turn it into an absolute path. The absolute// path name for a given file is not guaranteed to be unique.// Abs calls Clean on the result.func ( string) (string, error) {return abs()}func ( string) (string, error) {if IsAbs() {return Clean(), nil}, := os.Getwd()if != nil {return "",}return Join(, ), nil}// Rel returns a relative path that is lexically equivalent to targpath when// joined to basepath with an intervening separator. That is,// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.// On success, the returned path will always be relative to basepath,// even if basepath and targpath share no elements.// An error is returned if targpath can't be made relative to basepath or if// knowing the current working directory would be necessary to compute it.// Rel calls Clean on the result.func (, string) (string, error) {:= VolumeName():= VolumeName():= Clean():= Clean()if sameWord(, ) {return ".", nil}= [len():]= [len():]if == "." {= ""}// Can't use IsAbs - `\a` and `a` are both relative in Windows.:= len() > 0 && [0] == Separator:= len() > 0 && [0] == Separatorif != || !sameWord(, ) {return "", errors.New("Rel: can't make " + + " relative to " + )}// Position base[b0:bi] and targ[t0:ti] at the first differing elements.:= len():= len()var , , , intfor {for < && [] != Separator {++}for < && [] != Separator {++}if !sameWord([:], [:]) {break}if < {++}if < {++}==}if [:] == ".." {return "", errors.New("Rel: can't make " + + " relative to " + )}if != {// Base elements left. Must go up before going down.:= strings.Count([:], string(Separator)):= 2 + *3if != {+= 1 + -}:= make([]byte, ):= copy(, "..")for := 0; < ; ++ {[] = Separatorcopy([+1:], "..")+= 3}if != {[] = Separatorcopy([+1:], [:])}return string(), nil}return [:], nil}// SkipDir is used as a return value from WalkFuncs to indicate that// the directory named in the call is to be skipped. It is not returned// as an error by any function.var SkipDir error = fs.SkipDir// WalkFunc is the type of the function called by Walk to visit each each// file or directory.//// The path argument contains the argument to Walk as a prefix.// That is, if Walk is called with root argument "dir" and finds a file// named "a" in that directory, the walk function will be called with// argument "dir/a".//// The directory and file are joined with Join, which may clean the// directory name: if Walk is called with the root argument "x/../dir"// and finds a file named "a" in that directory, the walk function will// be called with argument "dir/a", not "x/../dir/a".//// The info argument is the fs.FileInfo for the named path.//// The error result returned by the function controls how Walk continues.// If the function returns the special value SkipDir, Walk skips the// current directory (path if info.IsDir() is true, otherwise path's// parent directory). Otherwise, if the function returns a non-nil error,// Walk stops entirely and returns that error.//// The err argument reports an error related to path, signaling that Walk// will not walk into that directory. The function can decide how to// handle that error; as described earlier, returning the error will// cause Walk to stop walking the entire tree.//// Walk calls the function with a non-nil err argument in two cases.//// First, if an os.Lstat on the root directory or any directory or file// in the tree fails, Walk calls the function with path set to that// directory or file's path, info set to nil, and err set to the error// from os.Lstat.//// Second, if a directory's Readdirnames method fails, Walk calls the// function with path set to the directory's path, info, set to an// fs.FileInfo describing the directory, and err set to the error from// Readdirnames.type WalkFunc func(path string, info fs.FileInfo, err error) errorvar lstat = os.Lstat // for testing// walkDir recursively descends path, calling walkDirFn.func ( string, fs.DirEntry, fs.WalkDirFunc) error {if := (, , nil); != nil || !.IsDir() {if == SkipDir && .IsDir() {// Successfully skipped directory.= nil}return}, := readDir()if != nil {// Second call, to report ReadDir error.= (, , )if != nil {return}}for , := range {:= Join(, .Name())if := (, , ); != nil {if == SkipDir {break}return}}return nil}// walk recursively descends path, calling walkFn.func ( string, fs.FileInfo, WalkFunc) error {if !.IsDir() {return (, , nil)}, := readDirNames():= (, , )// 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 != nil || != 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}for , := range {:= Join(, ), := lstat()if != nil {if := (, , ); != nil && != SkipDir {return}} else {= (, , )if != nil {if !.IsDir() || != SkipDir {return}}}}return nil}// WalkDir walks the file tree rooted at root, calling fn for each file or// directory in the tree, including root.//// All errors that arise visiting files and directories are filtered by fn:// see the fs.WalkDirFunc documentation for details.//// The files are walked in lexical order, which makes the output deterministic// but requires WalkDir to read an entire directory into memory before proceeding// to walk that directory.//// WalkDir does not follow symbolic links.func ( string, fs.WalkDirFunc) error {, := os.Lstat()if != nil {= (, nil, )} else {= walkDir(, &statDirEntry{}, )}if == SkipDir {return nil}return}type statDirEntry struct {info fs.FileInfo}func ( *statDirEntry) () string { return .info.Name() }func ( *statDirEntry) () bool { return .info.IsDir() }func ( *statDirEntry) () fs.FileMode { return .info.Mode().Type() }func ( *statDirEntry) () (fs.FileInfo, error) { return .info, nil }// Walk walks the file tree rooted at root, calling fn for each file or// directory in the tree, including root.//// All errors that arise visiting files and directories are filtered by fn:// see the WalkFunc documentation for details.//// The files are walked in lexical order, which makes the output deterministic// but requires Walk to read an entire directory into memory before proceeding// to walk that directory.//// Walk does not follow symbolic links.//// Walk is less efficient than WalkDir, introduced in Go 1.16,// which avoids calling os.Lstat on every visited file or directory.func ( string, WalkFunc) error {, := os.Lstat()if != nil {= (, nil, )} else {= walk(, , )}if == SkipDir {return nil}return}// readDir reads the directory named by dirname and returns// a sorted list of directory entries.func ( string) ([]fs.DirEntry, error) {, := os.Open()if != nil {return nil,}, := .ReadDir(-1).Close()if != nil {return nil,}sort.Slice(, func(, int) bool { return [].Name() < [].Name() })return , nil}// readDirNames reads the directory named by dirname and returns// a sorted list of directory entry names.func ( string) ([]string, error) {, := os.Open()if != nil {return nil,}, := .Readdirnames(-1).Close()if != nil {return nil,}sort.Strings()return , nil}// Base returns the last element of path.// Trailing path separators are removed before extracting the last element.// If the path is empty, Base returns ".".// If the path consists entirely of separators, Base returns a single separator.func ( string) string {if == "" {return "."}// Strip trailing slashes.for len() > 0 && os.IsPathSeparator([len()-1]) {= [0 : len()-1]}// Throw away volume name= [len(VolumeName()):]// Find the last element:= len() - 1for >= 0 && !os.IsPathSeparator([]) {--}if >= 0 {= [+1:]}// If empty now, it had only slashes.if == "" {return string(Separator)}return}// Dir returns all but the last element of path, typically the path's directory.// After dropping the final element, Dir calls Clean on the path and trailing// slashes are removed.// If the path is empty, Dir returns ".".// If the path consists entirely of separators, Dir returns a single separator.// The returned path does not end in a separator unless it is the root directory.func ( string) string {:= VolumeName():= len() - 1for >= len() && !os.IsPathSeparator([]) {--}:= Clean([len() : +1])if == "." && len() > 2 {// must be UNCreturn}return +}// 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 ( string) string {return [:volumeNameLen()]}