// 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. n := len(path) out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} r, dotdot := 0, 0 if rooted { out.append(Separator) r, dotdot = 1, 1 }
for r < n { switch { case os.IsPathSeparator(path[r]): // empty path element r++ case path[r] == '.' && r+1 == n: // . element r++ case path[r] == '.' && os.IsPathSeparator(path[r+1]): // ./ element r++
for r < len(path) && os.IsPathSeparator(path[r]) { r++ } if out.w == 0 && volumeNameLen(path[r:]) > 0 { // When joining prefix "." and an absolute path on Windows, // the prefix should not be removed. out.append('.') } case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): // .. element: remove to last separator r += 2 switch { case out.w > dotdot: // can backtrack out.w-- for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) { out.w-- } case !rooted: // cannot backtrack, but not rooted, so append .. element. if out.w > 0 { out.append(Separator) } out.append('.') out.append('.') dotdot = out.w } default: // real path element. // add slash if needed if rooted && out.w != 1 || !rooted && out.w != 0 { out.append(Separator) } // copy element for ; r < n && !os.IsPathSeparator(path[r]); r++ { out.append(path[r]) } } }
// Turn empty string into "." if out.w == 0 { out.append('.') }
但是 filepath.Join 函数就不太一样了,这个函数在 Plan9、Unix 和 Windows 三个操作系统类型下有着不同的实现。
funcjoin(elem []string)string { // If there's a bug here, fix the logic in ./path_plan9.go too. for i, e := range elem { if e != "" { return Clean(strings.Join(elem[i:], string(Separator))) } } return"" }
funcvolumeNameLen(path string)int { iflen(path) < 2 { return0 } // with drive letter c := path[0] if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { return2 } // 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 } } } return0 } funcjoin(elem []string)string { for i, e := range elem { if e != "" { return joinNonEmpty(elem[i:]) } } return"" }
// joinNonEmpty is like join, but it assumes that the first element is non-empty. funcjoinNonEmpty(elem []string)string { iflen(elem[0]) == 2 && elem[0][1] == ':' { // First element is drive letter without terminating slash. // Keep path relative to current directory on that drive. // Skip empty elements. i := 1 for ; i < len(elem); i++ { if elem[i] != "" { break } } return Clean(elem[0] + strings.Join(elem[i:], string(Separator))) } // The following logic prevents Join from inadvertently creating a // UNC path on Windows. Unless the first element is a UNC path, Join // shouldn't create a UNC path. See golang.org/issue/9167. p := Clean(strings.Join(elem, string(Separator))) if !isUNC(p) { return p } // p == UNC only allowed when the first element is a UNC path. head := Clean(elem[0]) if isUNC(head) { return p } // head + tail == UNC, but joining two non-UNC paths should not result // in a UNC path. Undo creation of UNC path. tail := Clean(strings.Join(elem[1:], string(Separator))) if head[len(head)-1] == Separator { return head + tail } return head + string(Separator) + tail }
// isUNC reports whether path is a UNC path. funcisUNC(path string)bool { return volumeNameLen(path) > 2 }
funcsameWord(a, b string)bool { return strings.EqualFold(a, b) }