1 """Common operations on Posix pathnames.
2
3 Instead of importing this module directly, import os and refer to
4 this module as os.path. The "os.path" name is an alias for this
5 module on Posix systems; on other systems (e.g. Windows),
6 os.path provides the same operations in a manner specific to that
7 platform, and is an alias to another module (e.g. ntpath).
8
9 Some of this can actually be useful on non-Posix systems too, e.g.
10 for manipulation of the pathname component of URLs.
11 """
12
13 # Strings representing various path-related bits and pieces.
14 # These are primarily for export; internally, they are hardcoded.
15 # Should be set before imports for resolving cyclic dependency.
16 curdir = '.'
17 pardir = '..'
18 extsep = '.'
19 sep = '/'
20 pathsep = ':'
21 defpath = '/bin:/usr/bin'
22 altsep = None
23 devnull = '/dev/null'
24
25 import os
26 import sys
27 import stat
28 import genericpath
29 from genericpath import *
30
31 __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
32 "basename","dirname","commonprefix","getsize","getmtime",
33 "getatime","getctime","islink","exists","lexists","isdir","isfile",
34 "ismount", "expanduser","expandvars","normpath","abspath",
35 "samefile","sameopenfile","samestat",
36 "curdir","pardir","sep","pathsep","defpath","altsep","extsep",
37 "devnull","realpath","supports_unicode_filenames","relpath",
38 "commonpath", "isjunction"]
39
40
41 def _get_sep(path):
42 if isinstance(path, bytes):
43 return b'/'
44 else:
45 return '/'
46
47 # Normalize the case of a pathname. Trivial in Posix, string.lower on Mac.
48 # On MS-DOS this may also turn slashes into backslashes; however, other
49 # normalizations (such as optimizing '../' away) are not allowed
50 # (another function should be defined to do that).
51
52 def normcase(s):
53 """Normalize case of pathname. Has no effect under Posix"""
54 return os.fspath(s)
55
56
57 # Return whether a path is absolute.
58 # Trivial in Posix, harder on the Mac or MS-DOS.
59
60 def isabs(s):
61 """Test whether a path is absolute"""
62 s = os.fspath(s)
63 sep = _get_sep(s)
64 return s.startswith(sep)
65
66
67 # Join pathnames.
68 # Ignore the previous parts if a part is absolute.
69 # Insert a '/' unless the first part is empty or already ends in '/'.
70
71 def join(a, *p):
72 """Join two or more pathname components, inserting '/' as needed.
73 If any component is an absolute path, all previous path components
74 will be discarded. An empty last part will result in a path that
75 ends with a separator."""
76 a = os.fspath(a)
77 sep = _get_sep(a)
78 path = a
79 try:
80 if not p:
81 path[:0] + sep #23780: Ensure compatible data type even if p is null.
82 for b in map(os.fspath, p):
83 if b.startswith(sep):
84 path = b
85 elif not path or path.endswith(sep):
86 path += b
87 else:
88 path += sep + b
89 except (TypeError, AttributeError, BytesWarning):
90 genericpath._check_arg_types('join', a, *p)
91 raise
92 return path
93
94
95 # Split a path in head (everything up to the last '/') and tail (the
96 # rest). If the path ends in '/', tail will be empty. If there is no
97 # '/' in the path, head will be empty.
98 # Trailing '/'es are stripped from head unless it is the root.
99
100 def split(p):
101 """Split a pathname. Returns tuple "(head, tail)" where "tail" is
102 everything after the final slash. Either part may be empty."""
103 p = os.fspath(p)
104 sep = _get_sep(p)
105 i = p.rfind(sep) + 1
106 head, tail = p[:i], p[i:]
107 if head and head != sep*len(head):
108 head = head.rstrip(sep)
109 return head, tail
110
111
112 # Split a path in root and extension.
113 # The extension is everything starting at the last dot in the last
114 # pathname component; the root is everything before that.
115 # It is always true that root + ext == p.
116
117 def splitext(p):
118 p = os.fspath(p)
119 if isinstance(p, bytes):
120 sep = b'/'
121 extsep = b'.'
122 else:
123 sep = '/'
124 extsep = '.'
125 return genericpath._splitext(p, sep, None, extsep)
126 splitext.__doc__ = genericpath._splitext.__doc__
127
128 # Split a pathname into a drive specification and the rest of the
129 # path. Useful on DOS/Windows/NT; on Unix, the drive is always empty.
130
131 def splitdrive(p):
132 """Split a pathname into drive and path. On Posix, drive is always
133 empty."""
134 p = os.fspath(p)
135 return p[:0], p
136
137
138 def splitroot(p):
139 """Split a pathname into drive, root and tail. On Posix, drive is always
140 empty; the root may be empty, a single slash, or two slashes. The tail
141 contains anything after the root. For example:
142
143 splitroot('foo/bar') == ('', '', 'foo/bar')
144 splitroot('/foo/bar') == ('', '/', 'foo/bar')
145 splitroot('//foo/bar') == ('', '//', 'foo/bar')
146 splitroot('///foo/bar') == ('', '/', '//foo/bar')
147 """
148 p = os.fspath(p)
149 if isinstance(p, bytes):
150 sep = b'/'
151 empty = b''
152 else:
153 sep = '/'
154 empty = ''
155 if p[:1] != sep:
156 # Relative path, e.g.: 'foo'
157 return empty, empty, p
158 elif p[1:2] != sep or p[2:3] == sep:
159 # Absolute path, e.g.: '/foo', '///foo', '////foo', etc.
160 return empty, sep, p[1:]
161 else:
162 # Precisely two leading slashes, e.g.: '//foo'. Implementation defined per POSIX, see
163 # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
164 return empty, p[:2], p[2:]
165
166
167 # Return the tail (basename) part of a path, same as split(path)[1].
168
169 def basename(p):
170 """Returns the final component of a pathname"""
171 p = os.fspath(p)
172 sep = _get_sep(p)
173 i = p.rfind(sep) + 1
174 return p[i:]
175
176
177 # Return the head (dirname) part of a path, same as split(path)[0].
178
179 def dirname(p):
180 """Returns the directory component of a pathname"""
181 p = os.fspath(p)
182 sep = _get_sep(p)
183 i = p.rfind(sep) + 1
184 head = p[:i]
185 if head and head != sep*len(head):
186 head = head.rstrip(sep)
187 return head
188
189
190 # Is a path a junction?
191
192 def isjunction(path):
193 """Test whether a path is a junction
194 Junctions are not a part of posix semantics"""
195 os.fspath(path)
196 return False
197
198
199 # Being true for dangling symbolic links is also useful.
200
201 def lexists(path):
202 """Test whether a path exists. Returns True for broken symbolic links"""
203 try:
204 os.lstat(path)
205 except (OSError, ValueError):
206 return False
207 return True
208
209
210 # Is a path a mount point?
211 # (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
212
213 def ismount(path):
214 """Test whether a path is a mount point"""
215 try:
216 s1 = os.lstat(path)
217 except (OSError, ValueError):
218 # It doesn't exist -- so not a mount point. :-)
219 return False
220 else:
221 # A symlink can never be a mount point
222 if stat.S_ISLNK(s1.st_mode):
223 return False
224
225 path = os.fspath(path)
226 if isinstance(path, bytes):
227 parent = join(path, b'..')
228 else:
229 parent = join(path, '..')
230 parent = realpath(parent)
231 try:
232 s2 = os.lstat(parent)
233 except (OSError, ValueError):
234 return False
235
236 dev1 = s1.st_dev
237 dev2 = s2.st_dev
238 if dev1 != dev2:
239 return True # path/.. on a different device as path
240 ino1 = s1.st_ino
241 ino2 = s2.st_ino
242 if ino1 == ino2:
243 return True # path/.. is the same i-node as path
244 return False
245
246
247 # Expand paths beginning with '~' or '~user'.
248 # '~' means $HOME; '~user' means that user's home directory.
249 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
250 # the path is returned unchanged (leaving error reporting to whatever
251 # function is called with the expanded path as argument).
252 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
253 # (A function should also be defined to do full *sh-style environment
254 # variable expansion.)
255
256 def expanduser(path):
257 """Expand ~ and ~user constructions. If user or $HOME is unknown,
258 do nothing."""
259 path = os.fspath(path)
260 if isinstance(path, bytes):
261 tilde = b'~'
262 else:
263 tilde = '~'
264 if not path.startswith(tilde):
265 return path
266 sep = _get_sep(path)
267 i = path.find(sep, 1)
268 if i < 0:
269 i = len(path)
270 if i == 1:
271 if 'HOME' not in os.environ:
272 try:
273 import pwd
274 except ImportError:
275 # pwd module unavailable, return path unchanged
276 return path
277 try:
278 userhome = pwd.getpwuid(os.getuid()).pw_dir
279 except KeyError:
280 # bpo-10496: if the current user identifier doesn't exist in the
281 # password database, return the path unchanged
282 return path
283 else:
284 userhome = os.environ['HOME']
285 else:
286 try:
287 import pwd
288 except ImportError:
289 # pwd module unavailable, return path unchanged
290 return path
291 name = path[1:i]
292 if isinstance(name, bytes):
293 name = str(name, 'ASCII')
294 try:
295 pwent = pwd.getpwnam(name)
296 except KeyError:
297 # bpo-10496: if the user name from the path doesn't exist in the
298 # password database, return the path unchanged
299 return path
300 userhome = pwent.pw_dir
301 # if no user home, return the path unchanged on VxWorks
302 if userhome is None and sys.platform == "vxworks":
303 return path
304 if isinstance(path, bytes):
305 userhome = os.fsencode(userhome)
306 root = b'/'
307 else:
308 root = '/'
309 userhome = userhome.rstrip(root)
310 return (userhome + path[i:]) or root
311
312
313 # Expand paths containing shell variable substitutions.
314 # This expands the forms $variable and ${variable} only.
315 # Non-existent variables are left unchanged.
316
317 _varprog = None
318 _varprogb = None
319
320 def expandvars(path):
321 """Expand shell variables of form $var and ${var}. Unknown variables
322 are left unchanged."""
323 path = os.fspath(path)
324 global _varprog, _varprogb
325 if isinstance(path, bytes):
326 if b'$' not in path:
327 return path
328 if not _varprogb:
329 import re
330 _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
331 search = _varprogb.search
332 start = b'{'
333 end = b'}'
334 environ = getattr(os, 'environb', None)
335 else:
336 if '$' not in path:
337 return path
338 if not _varprog:
339 import re
340 _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
341 search = _varprog.search
342 start = '{'
343 end = '}'
344 environ = os.environ
345 i = 0
346 while True:
347 m = search(path, i)
348 if not m:
349 break
350 i, j = m.span(0)
351 name = m.group(1)
352 if name.startswith(start) and name.endswith(end):
353 name = name[1:-1]
354 try:
355 if environ is None:
356 value = os.fsencode(os.environ[os.fsdecode(name)])
357 else:
358 value = environ[name]
359 except KeyError:
360 i = j
361 else:
362 tail = path[j:]
363 path = path[:i] + value
364 i = len(path)
365 path += tail
366 return path
367
368
369 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
370 # It should be understood that this may change the meaning of the path
371 # if it contains symbolic links!
372
373 try:
374 from posix import _path_normpath
375
376 except ImportError:
377 def normpath(path):
378 """Normalize path, eliminating double slashes, etc."""
379 path = os.fspath(path)
380 if isinstance(path, bytes):
381 sep = b'/'
382 empty = b''
383 dot = b'.'
384 dotdot = b'..'
385 else:
386 sep = '/'
387 empty = ''
388 dot = '.'
389 dotdot = '..'
390 if path == empty:
391 return dot
392 _, initial_slashes, path = splitroot(path)
393 comps = path.split(sep)
394 new_comps = []
395 for comp in comps:
396 if comp in (empty, dot):
397 continue
398 if (comp != dotdot or (not initial_slashes and not new_comps) or
399 (new_comps and new_comps[-1] == dotdot)):
400 new_comps.append(comp)
401 elif new_comps:
402 new_comps.pop()
403 comps = new_comps
404 path = initial_slashes + sep.join(comps)
405 return path or dot
406
407 else:
408 def normpath(path):
409 """Normalize path, eliminating double slashes, etc."""
410 path = os.fspath(path)
411 if isinstance(path, bytes):
412 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"."
413 return _path_normpath(path) or "."
414
415
416 def abspath(path):
417 """Return an absolute path."""
418 path = os.fspath(path)
419 if not isabs(path):
420 if isinstance(path, bytes):
421 cwd = os.getcwdb()
422 else:
423 cwd = os.getcwd()
424 path = join(cwd, path)
425 return normpath(path)
426
427
428 # Return a canonical path (i.e. the absolute location of a file on the
429 # filesystem).
430
431 def realpath(filename, *, strict=False):
432 """Return the canonical path of the specified filename, eliminating any
433 symbolic links encountered in the path."""
434 filename = os.fspath(filename)
435 path, ok = _joinrealpath(filename[:0], filename, strict, {})
436 return abspath(path)
437
438 # Join two paths, normalizing and eliminating any symbolic links
439 # encountered in the second path.
440 def _joinrealpath(path, rest, strict, seen):
441 if isinstance(path, bytes):
442 sep = b'/'
443 curdir = b'.'
444 pardir = b'..'
445 else:
446 sep = '/'
447 curdir = '.'
448 pardir = '..'
449
450 if isabs(rest):
451 rest = rest[1:]
452 path = sep
453
454 while rest:
455 name, _, rest = rest.partition(sep)
456 if not name or name == curdir:
457 # current dir
458 continue
459 if name == pardir:
460 # parent dir
461 if path:
462 path, name = split(path)
463 if name == pardir:
464 path = join(path, pardir, pardir)
465 else:
466 path = pardir
467 continue
468 newpath = join(path, name)
469 try:
470 st = os.lstat(newpath)
471 except OSError:
472 if strict:
473 raise
474 is_link = False
475 else:
476 is_link = stat.S_ISLNK(st.st_mode)
477 if not is_link:
478 path = newpath
479 continue
480 # Resolve the symbolic link
481 if newpath in seen:
482 # Already seen this path
483 path = seen[newpath]
484 if path is not None:
485 # use cached value
486 continue
487 # The symlink is not resolved, so we must have a symlink loop.
488 if strict:
489 # Raise OSError(errno.ELOOP)
490 os.stat(newpath)
491 else:
492 # Return already resolved part + rest of the path unchanged.
493 return join(newpath, rest), False
494 seen[newpath] = None # not resolved symlink
495 path, ok = _joinrealpath(path, os.readlink(newpath), strict, seen)
496 if not ok:
497 return join(path, rest), False
498 seen[newpath] = path # resolved symlink
499
500 return path, True
501
502
503 supports_unicode_filenames = (sys.platform == 'darwin')
504
505 def relpath(path, start=None):
506 """Return a relative version of a path"""
507
508 if not path:
509 raise ValueError("no path specified")
510
511 path = os.fspath(path)
512 if isinstance(path, bytes):
513 curdir = b'.'
514 sep = b'/'
515 pardir = b'..'
516 else:
517 curdir = '.'
518 sep = '/'
519 pardir = '..'
520
521 if start is None:
522 start = curdir
523 else:
524 start = os.fspath(start)
525
526 try:
527 start_list = [x for x in abspath(start).split(sep) if x]
528 path_list = [x for x in abspath(path).split(sep) if x]
529 # Work out how much of the filepath is shared by start and path.
530 i = len(commonprefix([start_list, path_list]))
531
532 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
533 if not rel_list:
534 return curdir
535 return join(*rel_list)
536 except (TypeError, AttributeError, BytesWarning, DeprecationWarning):
537 genericpath._check_arg_types('relpath', path, start)
538 raise
539
540
541 # Return the longest common sub-path of the sequence of paths given as input.
542 # The paths are not normalized before comparing them (this is the
543 # responsibility of the caller). Any trailing separator is stripped from the
544 # returned path.
545
546 def commonpath(paths):
547 """Given a sequence of path names, returns the longest common sub-path."""
548
549 if not paths:
550 raise ValueError('commonpath() arg is an empty sequence')
551
552 paths = tuple(map(os.fspath, paths))
553 if isinstance(paths[0], bytes):
554 sep = b'/'
555 curdir = b'.'
556 else:
557 sep = '/'
558 curdir = '.'
559
560 try:
561 split_paths = [path.split(sep) for path in paths]
562
563 try:
564 isabs, = set(p[:1] == sep for p in paths)
565 except ValueError:
566 raise ValueError("Can't mix absolute and relative paths") from None
567
568 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
569 s1 = min(split_paths)
570 s2 = max(split_paths)
571 common = s1
572 for i, c in enumerate(s1):
573 if c != s2[i]:
574 common = s1[:i]
575 break
576
577 prefix = sep if isabs else sep[:0]
578 return prefix + sep.join(common)
579 except (TypeError, AttributeError):
580 genericpath._check_arg_types('commonpath', *paths)
581 raise