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","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"]
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 # Return the tail (basename) part of a path, same as split(path)[1].
139
140 def basename(p):
141 """Returns the final component of a pathname"""
142 p = os.fspath(p)
143 sep = _get_sep(p)
144 i = p.rfind(sep) + 1
145 return p[i:]
146
147
148 # Return the head (dirname) part of a path, same as split(path)[0].
149
150 def dirname(p):
151 """Returns the directory component of a pathname"""
152 p = os.fspath(p)
153 sep = _get_sep(p)
154 i = p.rfind(sep) + 1
155 head = p[:i]
156 if head and head != sep*len(head):
157 head = head.rstrip(sep)
158 return head
159
160
161 # Is a path a symbolic link?
162 # This will always return false on systems where os.lstat doesn't exist.
163
164 def islink(path):
165 """Test whether a path is a symbolic link"""
166 try:
167 st = os.lstat(path)
168 except (OSError, ValueError, AttributeError):
169 return False
170 return stat.S_ISLNK(st.st_mode)
171
172 # Being true for dangling symbolic links is also useful.
173
174 def lexists(path):
175 """Test whether a path exists. Returns True for broken symbolic links"""
176 try:
177 os.lstat(path)
178 except (OSError, ValueError):
179 return False
180 return True
181
182
183 # Is a path a mount point?
184 # (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
185
186 def ismount(path):
187 """Test whether a path is a mount point"""
188 try:
189 s1 = os.lstat(path)
190 except (OSError, ValueError):
191 # It doesn't exist -- so not a mount point. :-)
192 return False
193 else:
194 # A symlink can never be a mount point
195 if stat.S_ISLNK(s1.st_mode):
196 return False
197
198 path = os.fspath(path)
199 if isinstance(path, bytes):
200 parent = join(path, b'..')
201 else:
202 parent = join(path, '..')
203 parent = realpath(parent)
204 try:
205 s2 = os.lstat(parent)
206 except (OSError, ValueError):
207 return False
208
209 dev1 = s1.st_dev
210 dev2 = s2.st_dev
211 if dev1 != dev2:
212 return True # path/.. on a different device as path
213 ino1 = s1.st_ino
214 ino2 = s2.st_ino
215 if ino1 == ino2:
216 return True # path/.. is the same i-node as path
217 return False
218
219
220 # Expand paths beginning with '~' or '~user'.
221 # '~' means $HOME; '~user' means that user's home directory.
222 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
223 # the path is returned unchanged (leaving error reporting to whatever
224 # function is called with the expanded path as argument).
225 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
226 # (A function should also be defined to do full *sh-style environment
227 # variable expansion.)
228
229 def expanduser(path):
230 """Expand ~ and ~user constructions. If user or $HOME is unknown,
231 do nothing."""
232 path = os.fspath(path)
233 if isinstance(path, bytes):
234 tilde = b'~'
235 else:
236 tilde = '~'
237 if not path.startswith(tilde):
238 return path
239 sep = _get_sep(path)
240 i = path.find(sep, 1)
241 if i < 0:
242 i = len(path)
243 if i == 1:
244 if 'HOME' not in os.environ:
245 try:
246 import pwd
247 except ImportError:
248 # pwd module unavailable, return path unchanged
249 return path
250 try:
251 userhome = pwd.getpwuid(os.getuid()).pw_dir
252 except KeyError:
253 # bpo-10496: if the current user identifier doesn't exist in the
254 # password database, return the path unchanged
255 return path
256 else:
257 userhome = os.environ['HOME']
258 else:
259 try:
260 import pwd
261 except ImportError:
262 # pwd module unavailable, return path unchanged
263 return path
264 name = path[1:i]
265 if isinstance(name, bytes):
266 name = str(name, 'ASCII')
267 try:
268 pwent = pwd.getpwnam(name)
269 except KeyError:
270 # bpo-10496: if the user name from the path doesn't exist in the
271 # password database, return the path unchanged
272 return path
273 userhome = pwent.pw_dir
274 # if no user home, return the path unchanged on VxWorks
275 if userhome is None and sys.platform == "vxworks":
276 return path
277 if isinstance(path, bytes):
278 userhome = os.fsencode(userhome)
279 root = b'/'
280 else:
281 root = '/'
282 userhome = userhome.rstrip(root)
283 return (userhome + path[i:]) or root
284
285
286 # Expand paths containing shell variable substitutions.
287 # This expands the forms $variable and ${variable} only.
288 # Non-existent variables are left unchanged.
289
290 _varprog = None
291 _varprogb = None
292
293 def expandvars(path):
294 """Expand shell variables of form $var and ${var}. Unknown variables
295 are left unchanged."""
296 path = os.fspath(path)
297 global _varprog, _varprogb
298 if isinstance(path, bytes):
299 if b'$' not in path:
300 return path
301 if not _varprogb:
302 import re
303 _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
304 search = _varprogb.search
305 start = b'{'
306 end = b'}'
307 environ = getattr(os, 'environb', None)
308 else:
309 if '$' not in path:
310 return path
311 if not _varprog:
312 import re
313 _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
314 search = _varprog.search
315 start = '{'
316 end = '}'
317 environ = os.environ
318 i = 0
319 while True:
320 m = search(path, i)
321 if not m:
322 break
323 i, j = m.span(0)
324 name = m.group(1)
325 if name.startswith(start) and name.endswith(end):
326 name = name[1:-1]
327 try:
328 if environ is None:
329 value = os.fsencode(os.environ[os.fsdecode(name)])
330 else:
331 value = environ[name]
332 except KeyError:
333 i = j
334 else:
335 tail = path[j:]
336 path = path[:i] + value
337 i = len(path)
338 path += tail
339 return path
340
341
342 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
343 # It should be understood that this may change the meaning of the path
344 # if it contains symbolic links!
345
346 try:
347 from posix import _path_normpath
348
349 except ImportError:
350 def normpath(path):
351 """Normalize path, eliminating double slashes, etc."""
352 path = os.fspath(path)
353 if isinstance(path, bytes):
354 sep = b'/'
355 empty = b''
356 dot = b'.'
357 dotdot = b'..'
358 else:
359 sep = '/'
360 empty = ''
361 dot = '.'
362 dotdot = '..'
363 if path == empty:
364 return dot
365 initial_slashes = path.startswith(sep)
366 # POSIX allows one or two initial slashes, but treats three or more
367 # as single slash.
368 # (see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13)
369 if (initial_slashes and
370 path.startswith(sep*2) and not path.startswith(sep*3)):
371 initial_slashes = 2
372 comps = path.split(sep)
373 new_comps = []
374 for comp in comps:
375 if comp in (empty, dot):
376 continue
377 if (comp != dotdot or (not initial_slashes and not new_comps) or
378 (new_comps and new_comps[-1] == dotdot)):
379 new_comps.append(comp)
380 elif new_comps:
381 new_comps.pop()
382 comps = new_comps
383 path = sep.join(comps)
384 if initial_slashes:
385 path = sep*initial_slashes + path
386 return path or dot
387
388 else:
389 def normpath(path):
390 """Normalize path, eliminating double slashes, etc."""
391 path = os.fspath(path)
392 if isinstance(path, bytes):
393 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"."
394 return _path_normpath(path) or "."
395
396
397 def abspath(path):
398 """Return an absolute path."""
399 path = os.fspath(path)
400 if not isabs(path):
401 if isinstance(path, bytes):
402 cwd = os.getcwdb()
403 else:
404 cwd = os.getcwd()
405 path = join(cwd, path)
406 return normpath(path)
407
408
409 # Return a canonical path (i.e. the absolute location of a file on the
410 # filesystem).
411
412 def realpath(filename, *, strict=False):
413 """Return the canonical path of the specified filename, eliminating any
414 symbolic links encountered in the path."""
415 filename = os.fspath(filename)
416 path, ok = _joinrealpath(filename[:0], filename, strict, {})
417 return abspath(path)
418
419 # Join two paths, normalizing and eliminating any symbolic links
420 # encountered in the second path.
421 def _joinrealpath(path, rest, strict, seen):
422 if isinstance(path, bytes):
423 sep = b'/'
424 curdir = b'.'
425 pardir = b'..'
426 else:
427 sep = '/'
428 curdir = '.'
429 pardir = '..'
430
431 if isabs(rest):
432 rest = rest[1:]
433 path = sep
434
435 while rest:
436 name, _, rest = rest.partition(sep)
437 if not name or name == curdir:
438 # current dir
439 continue
440 if name == pardir:
441 # parent dir
442 if path:
443 path, name = split(path)
444 if name == pardir:
445 path = join(path, pardir, pardir)
446 else:
447 path = pardir
448 continue
449 newpath = join(path, name)
450 try:
451 st = os.lstat(newpath)
452 except OSError:
453 if strict:
454 raise
455 is_link = False
456 else:
457 is_link = stat.S_ISLNK(st.st_mode)
458 if not is_link:
459 path = newpath
460 continue
461 # Resolve the symbolic link
462 if newpath in seen:
463 # Already seen this path
464 path = seen[newpath]
465 if path is not None:
466 # use cached value
467 continue
468 # The symlink is not resolved, so we must have a symlink loop.
469 if strict:
470 # Raise OSError(errno.ELOOP)
471 os.stat(newpath)
472 else:
473 # Return already resolved part + rest of the path unchanged.
474 return join(newpath, rest), False
475 seen[newpath] = None # not resolved symlink
476 path, ok = _joinrealpath(path, os.readlink(newpath), strict, seen)
477 if not ok:
478 return join(path, rest), False
479 seen[newpath] = path # resolved symlink
480
481 return path, True
482
483
484 supports_unicode_filenames = (sys.platform == 'darwin')
485
486 def relpath(path, start=None):
487 """Return a relative version of a path"""
488
489 if not path:
490 raise ValueError("no path specified")
491
492 path = os.fspath(path)
493 if isinstance(path, bytes):
494 curdir = b'.'
495 sep = b'/'
496 pardir = b'..'
497 else:
498 curdir = '.'
499 sep = '/'
500 pardir = '..'
501
502 if start is None:
503 start = curdir
504 else:
505 start = os.fspath(start)
506
507 try:
508 start_list = [x for x in abspath(start).split(sep) if x]
509 path_list = [x for x in abspath(path).split(sep) if x]
510 # Work out how much of the filepath is shared by start and path.
511 i = len(commonprefix([start_list, path_list]))
512
513 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
514 if not rel_list:
515 return curdir
516 return join(*rel_list)
517 except (TypeError, AttributeError, BytesWarning, DeprecationWarning):
518 genericpath._check_arg_types('relpath', path, start)
519 raise
520
521
522 # Return the longest common sub-path of the sequence of paths given as input.
523 # The paths are not normalized before comparing them (this is the
524 # responsibility of the caller). Any trailing separator is stripped from the
525 # returned path.
526
527 def commonpath(paths):
528 """Given a sequence of path names, returns the longest common sub-path."""
529
530 if not paths:
531 raise ValueError('commonpath() arg is an empty sequence')
532
533 paths = tuple(map(os.fspath, paths))
534 if isinstance(paths[0], bytes):
535 sep = b'/'
536 curdir = b'.'
537 else:
538 sep = '/'
539 curdir = '.'
540
541 try:
542 split_paths = [path.split(sep) for path in paths]
543
544 try:
545 isabs, = set(p[:1] == sep for p in paths)
546 except ValueError:
547 raise ValueError("Can't mix absolute and relative paths") from None
548
549 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
550 s1 = min(split_paths)
551 s2 = max(split_paths)
552 common = s1
553 for i, c in enumerate(s1):
554 if c != s2[i]:
555 common = s1[:i]
556 break
557
558 prefix = sep if isabs else sep[:0]
559 return prefix + sep.join(common)
560 except (TypeError, AttributeError):
561 genericpath._check_arg_types('commonpath', *paths)
562 raise