# Module 'ntpath' -- common operations on WinNT/Win95 pathnames
"""Common pathname manipulations, WindowsNT/95 version.
Instead of importing this module directly, import os and refer to this
# strings representing various path-related bits and pieces
# These are primarily for export; internally, they are hardcoded.
# Should be set before imports for resolving cyclic dependency.
from genericpath import *
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
"basename","dirname","commonprefix","getsize","getmtime",
"getatime","getctime", "islink","exists","lexists","isdir","isfile",
"ismount", "expanduser","expandvars","normpath","abspath",
"curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
"samefile", "sameopenfile", "samestat", "commonpath"]
if isinstance(path, bytes):
# Normalize the case of a pathname and map slashes to backslashes.
# Other normalizations (such as optimizing '../' away) are not done
# (this is done by normpath).
"""Normalize case of pathname.
Makes all characters lowercase and all slashes into backslashes."""
return s.replace(b'/', b'\\').lower()
return s.replace('/', '\\').lower()
# Return whether a path is absolute.
# Trivial in Posix, harder on Windows.
# For Windows it is absolute if it starts with a slash or backslash (current
# volume), or if a pathname after the volume-letter-and-colon or UNC-resource
# starts with a slash or backslash.
"""Test whether a path is absolute"""
# Paths beginning with \\?\ are always absolute, but do not
# necessarily contain a drive.
if s.replace(b'/', b'\\').startswith(b'\\\\?\\'):
if s.replace('/', '\\').startswith('\\\\?\\'):
return len(s) > 0 and s[0] in _get_bothseps(s)
# Join two (or more) paths.
if isinstance(path, bytes):
path[:0] + sep #23780: Ensure compatible data type even if p is null.
result_drive, result_path = splitdrive(path)
for p in map(os.fspath, paths):
p_drive, p_path = splitdrive(p)
if p_path and p_path[0] in seps:
# Second path is absolute
if p_drive or not result_drive:
elif p_drive and p_drive != result_drive:
if p_drive.lower() != result_drive.lower():
# Different drives => ignore the first path entirely
# Same drive in different case
# Second path is relative to the first
if result_path and result_path[-1] not in seps:
result_path = result_path + sep
result_path = result_path + p_path
## add separator between UNC and non-absolute path
if (result_path and result_path[0] not in seps and
result_drive and result_drive[-1:] != colon):
return result_drive + sep + result_path
return result_drive + result_path
except (TypeError, AttributeError, BytesWarning):
genericpath._check_arg_types('join', path, *paths)
# Split a path in a drive specification (a drive letter followed by a
# colon) and the path specification.
# It is always true that drivespec + pathspec == p
"""Split a pathname into drive/UNC sharepoint and relative path specifiers.
Returns a 2-tuple (drive_or_unc, path); either part may be empty.
result[0] + result[1] == p
If the path contained a drive letter, drive_or_unc will contain everything
up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
If the path contained a UNC path, the drive_or_unc will contain the host name
and share up to but not including the fourth directory separator character.
e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
Paths cannot contain both a drive letter and a UNC path.
normp = p.replace(altsep, sep)
if (normp[0:2] == sep*2) and (normp[2:3] != sep):
# vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
# \\machine\mountpoint\directory\etc\...
# directory ^^^^^^^^^^^^^^^
index = normp.find(sep, 2)
index2 = normp.find(sep, index + 1)
# a UNC path can't have two slashes in a row
# (after the initial two)
return p[:index2], p[index2:]
# Split a path in head (everything up to the last '/') and tail (the
# rest). After the trailing '/' is stripped, the invariant
# join(head, tail) == p holds.
# The resulting head won't end in '/' unless it is the root.
Return tuple (head, tail) where tail is everything after the final slash.
Either part may be empty."""
# set i to index beyond p's last slash
while i and p[i-1] not in seps:
head, tail = p[:i], p[i:] # now tail has no slashes
# remove trailing slashes from head, unless it's all slashes
head = head.rstrip(seps) or head
# Split a path in root and extension.
# The extension is everything starting at the last dot in the last
# pathname component; the root is everything before that.
# It is always true that root + ext == p.
return genericpath._splitext(p, b'\\', b'/', b'.')
return genericpath._splitext(p, '\\', '/', '.')
splitext.__doc__ = genericpath._splitext.__doc__
# Return the tail (basename) part of a path.
"""Returns the final component of a pathname"""
# Return the head (dirname) part of a path.
"""Returns the directory component of a pathname"""
# Is a path a symbolic link?
# This will always return false on systems where os.lstat doesn't exist.
"""Test whether a path is a symbolic link.
This will always return false for Windows prior to 6.0.
except (OSError, ValueError, AttributeError):
return stat.S_ISLNK(st.st_mode)
# Being true for dangling symbolic links is also useful.
"""Test whether a path exists. Returns True for broken symbolic links"""
except (OSError, ValueError):
# Is a path a mount point?
# Any drive letter root (eg c:\)
# Any share UNC (eg \\server\share)
# Any volume mounted on a filesystem folder
# No one method detects all three situations. Historically we've lexically
# detected drive letter roots and share UNCs. The canonical approach to
# detecting mounted volumes (querying the reparse tag) fails for the most
# common case: drive letter roots. The alternative which uses GetVolumePathName
# fails if the drive letter is the result of a SUBST.
from nt import _getvolumepathname
_getvolumepathname = None
"""Test whether a path is a mount point (a drive root, the root of a
share, or a mounted volume)"""
seps = _get_bothseps(path)
root, rest = splitdrive(path)
if root and root[0] in seps:
return (not rest) or (rest in seps)
return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps)
# Expand paths beginning with '~' or '~user'.
# '~' means $HOME; '~user' means that user's home directory.
# If the path doesn't begin with '~', or if the user or $HOME is unknown,
# the path is returned unchanged (leaving error reporting to whatever
# function is called with the expanded path as argument).
# See also module 'glob' for expansion of *, ? and [...] in pathnames.
# (A function should also be defined to do full *sh-style environment
"""Expand ~ and ~user constructs.
If user or $HOME is unknown, do nothing."""
if isinstance(path, bytes):
if not path.startswith(tilde):
while i < n and path[i] not in _get_bothseps(path):
if 'USERPROFILE' in os.environ:
userhome = os.environ['USERPROFILE']
elif not 'HOMEPATH' in os.environ:
drive = os.environ['HOMEDRIVE']
userhome = join(drive, os.environ['HOMEPATH'])
if isinstance(path, bytes):
userhome = os.fsencode(userhome)
userhome = join(dirname(userhome), path[1:i])
return userhome + path[i:]
# Expand paths containing shell variable substitutions.
# The following rules apply:
# - no expansion within single quotes
# - '$$' is translated into '$'
# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
# - ${varname} is accepted.
# - $varname is accepted.
# - %varname% is accepted.
# - varnames can be made out of letters, digits and the characters '_-'
# (though is not verified in the ${varname} and %varname% cases)
# XXX With COMMAND.COM you can use any characters in a variable name,
"""Expand shell variables of the forms $var, ${var} and %var%.
Unknown variables are left unchanged."""
if isinstance(path, bytes):
if b'$' not in path and b'%' not in path:
varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
environ = getattr(os, 'environb', None)
if '$' not in path and '%' not in path:
varchars = string.ascii_letters + string.digits + '_-'
if c == quote: # no expansion within single quotes
res += c + path[:index + 1]
elif c == percent: # variable or '%'
if path[index + 1:index + 2] == percent:
index = path.index(percent)
value = os.fsencode(os.environ[os.fsdecode(var)])
value = percent + var + percent
elif c == dollar: # variable or '$$'
if path[index + 1:index + 2] == dollar:
elif path[index + 1:index + 2] == brace:
index = path.index(rbrace)
res += dollar + brace + path
value = os.fsencode(os.environ[os.fsdecode(var)])
value = dollar + brace + var + rbrace
c = path[index:index + 1]
while c and c in varchars:
c = path[index:index + 1]
value = os.fsencode(os.environ[os.fsdecode(var)])
# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
# Previously, this function also truncated pathnames to 8+3 format,
# but as this module is called "ntpath", that's obviously wrong!
"""Normalize path, eliminating double slashes, etc."""
if isinstance(path, bytes):
special_prefixes = (b'\\\\.\\', b'\\\\?\\')
special_prefixes = ('\\\\.\\', '\\\\?\\')
if path.startswith(special_prefixes):
# in the case of paths with these prefixes:
# do not do any normalization, but return the path
# unchanged apart from the call to os.fspath()
path = path.replace(altsep, sep)
prefix, path = splitdrive(path)
# collapse initial backslashes
if not comps[i] or comps[i] == curdir:
if i > 0 and comps[i-1] != pardir:
elif i == 0 and prefix.endswith(sep):
# If the path is now empty, substitute '.'
if not prefix and not comps:
return prefix + sep.join(comps)
def _abspath_fallback(path):