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 filepath
import (
)
// 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 string
buf []byte
w int
volAndPath string
volLen 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) ( ...byte) {
.buf = append(, .buf...)
.w += len()
}
func ( *lazybuf) () string {
if .buf == nil {
return .volAndPath[:.volLen+.w]
}
return .volAndPath[:.volLen] + string(.buf[:.w])
}
const (
Separator = os.PathSeparator
ListSeparator = 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 ".".
//
// On Windows, Clean does not modify the volume name other than to replace
// occurrences of "/" with `\`.
// For example, Clean("//host/share/../x") returns `\\host\share\x`.
//
// See also Rob Pike, “Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,”
// https://9p.io/sys/doc/lexnames.html
func ( string) string {
:=
:= volumeNameLen()
= [:]
if == "" {
if > 1 && os.IsPathSeparator([0]) && os.IsPathSeparator([1]) {
// should be UNC
return 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, 0
if {
.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
+= 2
switch {
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 needed
if && .w != 1 || ! && .w != 0 {
.append(Separator)
}
// copy element
for ; < && !os.IsPathSeparator([]); ++ {
.append([])
}
}
}
// Turn empty string into "."
if .w == 0 {
.append('.')
}
postClean(&) // avoid creating absolute paths on Windows
return FromSlash(.string())
}
// IsLocal reports whether path, using lexical analysis only, has all of these properties:
//
// - is within the subtree rooted at the directory in which path is evaluated
// - is not an absolute path
// - is not empty
// - on Windows, is not a reserved name such as "NUL"
//
// If IsLocal(path) returns true, then
// Join(base, path) will always produce a path contained within base and
// Clean(path) will always produce an unrooted path with no ".." path elements.
//
// IsLocal is a purely lexical operation.
// In particular, it does not account for the effect of any symbolic links
// that may exist in the filesystem.
func ( string) bool {
return isLocal()
}
func ( string) bool {
if IsAbs() || == "" {
return false
}
:= false
for := ; != ""; {
var string
, , _ = strings.Cut(, "/")
if == "." || == ".." {
= true
break
}
}
if {
= Clean()
}
if == ".." || strings.HasPrefix(, "../") {
return false
}
return true
}
// 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() - 1
for >= 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 == "." {
= ""
} else if == "" && volumeNameLen() > 2 /* isUNC */ {
// Treat any targetpath matching `\\host\share` basepath as absolute path.
= string(Separator)
}
// Can't use IsAbs - `\a` and `a` are both relative in Windows.
:= len() > 0 && [0] == Separator
:= len() > 0 && [0] == Separator
if != || !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 , , , int
for {
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 + *3
if != {
+= 1 + -
}
:= make([]byte, )
:= copy(, "..")
for := 0; < ; ++ {
[] = Separator
copy([+1:], "..")
+= 3
}
if != {
[] = Separator
copy([+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
// SkipAll is used as a return value from WalkFuncs to indicate that
// all remaining files and directories are to be skipped. It is not returned
// as an error by any function.
var SkipAll error = fs.SkipAll
// WalkFunc is the type of the function called by Walk to visit 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). If the function returns the special value SkipAll,
// Walk skips all remaining files and directories. 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) error
var 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 {
if == SkipDir && .IsDir() {
= 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 or SkipAll, 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.
//
// WalkDir calls fn with paths that use the separator character appropriate
// for the operating system. This is unlike [io/fs.WalkDir], which always
// uses slash separated paths.
func ( string, fs.WalkDirFunc) error {
, := os.Lstat()
if != nil {
= (, nil, )
} else {
= walkDir(, &statDirEntry{}, )
}
if == SkipDir || == SkipAll {
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 }
func ( *statDirEntry) () string {
return fs.FormatDirEntry()
}
// 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 || == SkipAll {
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() - 1
for >= 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() - 1
for >= len() && !os.IsPathSeparator([]) {
--
}
:= Clean([len() : +1])
if == "." && len() > 2 {
// must be UNC
return
}
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 FromSlash([:volumeNameLen()])
}
The pages are generated with Golds v0.6.7. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |