posixpath.py 12.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
"""Common operations on Posix pathnames.

Instead of importing this module directly, import os and refer to
this module as os.path.  The "os.path" name is an alias for this
module on Posix systems; on other systems (e.g. Mac, Windows),
os.path provides the same operations in a manner specific to that
platform, and is an alias to another module (e.g. macpath, ntpath).

Some of this can actually be useful on non-Posix systems too, e.g.
for manipulation of the pathname component of URLs.
11
"""
12 13

import os
14
import stat
15
import genericpath
16
import warnings
17
from genericpath import *
Guido van Rossum's avatar
Guido van Rossum committed
18

19 20
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
           "basename","dirname","commonprefix","getsize","getmtime",
21 22
           "getatime","getctime","islink","exists","lexists","isdir","isfile",
           "ismount","walk","expanduser","expandvars","normpath","abspath",
23
           "samefile","sameopenfile","samestat",
24
           "curdir","pardir","sep","pathsep","defpath","altsep","extsep",
25
           "devnull","realpath","supports_unicode_filenames","relpath"]
Guido van Rossum's avatar
Guido van Rossum committed
26

27 28 29 30 31 32 33 34
# strings representing various path-related bits and pieces
curdir = '.'
pardir = '..'
extsep = '.'
sep = '/'
pathsep = ':'
defpath = ':/bin:/usr/bin'
altsep = None
35
devnull = '/dev/null'
36

Guido van Rossum's avatar
Guido van Rossum committed
37 38 39 40 41 42
# Normalize the case of a pathname.  Trivial in Posix, string.lower on Mac.
# On MS-DOS this may also turn slashes into backslashes; however, other
# normalizations (such as optimizing '../' away) are not allowed
# (another function should be defined to do that).

def normcase(s):
43 44
    """Normalize case of pathname.  Has no effect under Posix"""
    return s
Guido van Rossum's avatar
Guido van Rossum committed
45 46


Jeremy Hylton's avatar
Jeremy Hylton committed
47
# Return whether a path is absolute.
Guido van Rossum's avatar
Guido van Rossum committed
48 49 50
# Trivial in Posix, harder on the Mac or MS-DOS.

def isabs(s):
51
    """Test whether a path is absolute"""
52
    return s.startswith('/')
Guido van Rossum's avatar
Guido van Rossum committed
53 54


55 56
# Join pathnames.
# Ignore the previous parts if a part is absolute.
Guido van Rossum's avatar
Guido van Rossum committed
57
# Insert a '/' unless the first part is empty or already ends in '/'.
Guido van Rossum's avatar
Guido van Rossum committed
58

59
def join(a, *p):
60 61 62
    """Join two or more pathname components, inserting '/' as needed.
    If any component is an absolute path, all previous path components
    will be discarded."""
63 64
    path = a
    for b in p:
65
        if b.startswith('/'):
66
            path = b
67 68
        elif path == '' or path.endswith('/'):
            path +=  b
69
        else:
70
            path += '/' + b
71
    return path
Guido van Rossum's avatar
Guido van Rossum committed
72 73


74
# Split a path in head (everything up to the last '/') and tail (the
75 76 77
# rest).  If the path ends in '/', tail will be empty.  If there is no
# '/' in the path, head  will be empty.
# Trailing '/'es are stripped from head unless it is the root.
Guido van Rossum's avatar
Guido van Rossum committed
78

Guido van Rossum's avatar
Guido van Rossum committed
79
def split(p):
Tim Peters's avatar
Tim Peters committed
80
    """Split a pathname.  Returns tuple "(head, tail)" where "tail" is
Fred Drake's avatar
Fred Drake committed
81
    everything after the final slash.  Either part may be empty."""
82
    i = p.rfind('/') + 1
83
    head, tail = p[:i], p[i:]
84
    if head and head != '/'*len(head):
85
        head = head.rstrip('/')
86
    return head, tail
Guido van Rossum's avatar
Guido van Rossum committed
87 88


Guido van Rossum's avatar
Guido van Rossum committed
89
# Split a path in root and extension.
90
# The extension is everything starting at the last dot in the last
Guido van Rossum's avatar
Guido van Rossum committed
91
# pathname component; the root is everything before that.
Guido van Rossum's avatar
Guido van Rossum committed
92 93
# It is always true that root + ext == p.

Guido van Rossum's avatar
Guido van Rossum committed
94
def splitext(p):
95 96
    return genericpath._splitext(p, sep, altsep, extsep)
splitext.__doc__ = genericpath._splitext.__doc__
Guido van Rossum's avatar
Guido van Rossum committed
97

Guido van Rossum's avatar
Guido van Rossum committed
98 99 100 101
# Split a pathname into a drive specification and the rest of the
# path.  Useful on DOS/Windows/NT; on Unix, the drive is always empty.

def splitdrive(p):
Tim Peters's avatar
Tim Peters committed
102
    """Split a pathname into drive and path. On Posix, drive is always
Fred Drake's avatar
Fred Drake committed
103
    empty."""
104
    return '', p
Guido van Rossum's avatar
Guido van Rossum committed
105 106


107
# Return the tail (basename) part of a path, same as split(path)[1].
Guido van Rossum's avatar
Guido van Rossum committed
108

Guido van Rossum's avatar
Guido van Rossum committed
109
def basename(p):
110
    """Returns the final component of a pathname"""
111 112
    i = p.rfind('/') + 1
    return p[i:]
Guido van Rossum's avatar
Guido van Rossum committed
113 114


115
# Return the head (dirname) part of a path, same as split(path)[0].
116 117

def dirname(p):
118
    """Returns the directory component of a pathname"""
119 120 121 122 123
    i = p.rfind('/') + 1
    head = p[:i]
    if head and head != '/'*len(head):
        head = head.rstrip('/')
    return head
124 125


Guido van Rossum's avatar
Guido van Rossum committed
126
# Is a path a symbolic link?
127
# This will always return false on systems where os.lstat doesn't exist.
Guido van Rossum's avatar
Guido van Rossum committed
128 129

def islink(path):
130 131 132 133
    """Test whether a path is a symbolic link"""
    try:
        st = os.lstat(path)
    except (os.error, AttributeError):
134
        return False
135
    return stat.S_ISLNK(st.st_mode)
Guido van Rossum's avatar
Guido van Rossum committed
136

137 138 139 140 141 142 143 144 145 146 147
# Being true for dangling symbolic links is also useful.

def lexists(path):
    """Test whether a path exists.  Returns True for broken symbolic links"""
    try:
        st = os.lstat(path)
    except os.error:
        return False
    return True


148
# Are two filenames really pointing to the same file?
Guido van Rossum's avatar
Guido van Rossum committed
149

150
def samefile(f1, f2):
151 152 153 154
    """Test whether two pathnames reference the same actual file"""
    s1 = os.stat(f1)
    s2 = os.stat(f2)
    return samestat(s1, s2)
155 156 157 158


# Are two open files really referencing the same file?
# (Not necessarily the same file descriptor!)
Guido van Rossum's avatar
Guido van Rossum committed
159

160
def sameopenfile(fp1, fp2):
161 162 163 164
    """Test whether two open file objects reference the same file"""
    s1 = os.fstat(fp1)
    s2 = os.fstat(fp2)
    return samestat(s1, s2)
165 166 167 168


# Are two stat buffers (obtained from stat, fstat or lstat)
# describing the same file?
Guido van Rossum's avatar
Guido van Rossum committed
169

170
def samestat(s1, s2):
171
    """Test whether two stat buffers reference the same file"""
172 173
    return s1.st_ino == s2.st_ino and \
           s1.st_dev == s2.st_dev
Guido van Rossum's avatar
Guido van Rossum committed
174 175 176


# Is a path a mount point?
177
# (Does this work for all UNIXes?  Is it even guaranteed to work by Posix?)
Guido van Rossum's avatar
Guido van Rossum committed
178

Guido van Rossum's avatar
Guido van Rossum committed
179
def ismount(path):
180 181
    """Test whether a path is a mount point"""
    try:
182 183
        s1 = os.lstat(path)
        s2 = os.lstat(join(path, '..'))
184
    except os.error:
185
        return False # It doesn't exist -- so not a mount point :-)
186 187
    dev1 = s1.st_dev
    dev2 = s2.st_dev
188
    if dev1 != dev2:
189
        return True     # path/.. on a different device as path
190 191
    ino1 = s1.st_ino
    ino2 = s2.st_ino
192
    if ino1 == ino2:
193 194
        return True     # path/.. is the same i-node as path
    return False
Guido van Rossum's avatar
Guido van Rossum committed
195 196 197


# Directory tree walk.
Guido van Rossum's avatar
Guido van Rossum committed
198 199 200
# For each directory under top (including top itself, but excluding
# '.' and '..'), func(arg, dirname, filenames) is called, where
# dirname is the name of the directory and filenames is the list
201
# of files (and subdirectories etc.) in the directory.
Guido van Rossum's avatar
Guido van Rossum committed
202
# The func may modify the filenames list, to implement a filter,
Guido van Rossum's avatar
Guido van Rossum committed
203
# or to impose a different order of visiting.
Guido van Rossum's avatar
Guido van Rossum committed
204

Guido van Rossum's avatar
Guido van Rossum committed
205
def walk(top, func, arg):
206 207 208 209 210 211 212 213 214 215 216 217 218
    """Directory tree walk with callback function.

    For each directory in the directory tree rooted at top (including top
    itself, but excluding '.' and '..'), call func(arg, dirname, fnames).
    dirname is the name of the directory, and fnames a list of the names of
    the files and subdirectories in dirname (excluding '.' and '..').  func
    may modify the fnames list in-place (e.g. via del or slice assignment),
    and walk will only recurse into the subdirectories whose names remain in
    fnames; this can be used to implement a filter, or to impose a specific
    order of visiting.  No semantics are defined for, or required of, arg,
    beyond that arg is always passed to func.  It can be used, e.g., to pass
    a filename pattern, or a mutable object designed to accumulate
    statistics.  Passing None for arg is common."""
219
    warnings.warnpy3k("In 3.x, os.path.walk is removed in favor of os.walk.")
220 221 222 223 224 225
    try:
        names = os.listdir(top)
    except os.error:
        return
    func(arg, top, names)
    for name in names:
Tim Peters's avatar
Tim Peters committed
226
        name = join(top, name)
227 228 229 230
        try:
            st = os.lstat(name)
        except os.error:
            continue
231
        if stat.S_ISDIR(st.st_mode):
Tim Peters's avatar
Tim Peters committed
232
            walk(name, func, arg)
Guido van Rossum's avatar
Guido van Rossum committed
233 234 235 236 237 238 239 240 241 242 243 244


# 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
# variable expansion.)

def expanduser(path):
Tim Peters's avatar
Tim Peters committed
245
    """Expand ~ and ~user constructions.  If user or $HOME is unknown,
Fred Drake's avatar
Fred Drake committed
246
    do nothing."""
247
    if not path.startswith('~'):
248
        return path
249 250 251
    i = path.find('/', 1)
    if i < 0:
        i = len(path)
252
    if i == 1:
253
        if 'HOME' not in os.environ:
254
            import pwd
255
            userhome = pwd.getpwuid(os.getuid()).pw_dir
256 257
        else:
            userhome = os.environ['HOME']
258 259 260 261 262 263
    else:
        import pwd
        try:
            pwent = pwd.getpwnam(path[1:i])
        except KeyError:
            return path
264
        userhome = pwent.pw_dir
265
    userhome = userhome.rstrip('/')
266
    return userhome + path[i:]
267 268 269


# Expand paths containing shell variable substitutions.
270
# This expands the forms $variable and ${variable} only.
Jeremy Hylton's avatar
Jeremy Hylton committed
271
# Non-existent variables are left unchanged.
272 273

_varprog = None
274 275

def expandvars(path):
276
    """Expand shell variables of form $var and ${var}.  Unknown variables
Fred Drake's avatar
Fred Drake committed
277
    are left unchanged."""
278 279 280 281 282 283 284
    global _varprog
    if '$' not in path:
        return path
    if not _varprog:
        import re
        _varprog = re.compile(r'\$(\w+|\{[^}]*\})')
    i = 0
285
    while True:
286 287 288 289 290
        m = _varprog.search(path, i)
        if not m:
            break
        i, j = m.span(0)
        name = m.group(1)
291
        if name.startswith('{') and name.endswith('}'):
292
            name = name[1:-1]
293
        if name in os.environ:
294 295 296
            tail = path[j:]
            path = path[:i] + os.environ[name]
            i = len(path)
297
            path += tail
298 299 300
        else:
            i = j
    return path
301 302 303 304 305 306 307


# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
# It should be understood that this may change the meaning of the path
# if it contains symbolic links!

def normpath(path):
308
    """Normalize path, eliminating double slashes, etc."""
309 310
    if path == '':
        return '.'
311 312 313
    initial_slashes = path.startswith('/')
    # POSIX allows one or two initial slashes, but treats three or more
    # as single slash.
Tim Peters's avatar
Tim Peters committed
314
    if (initial_slashes and
315 316
        path.startswith('//') and not path.startswith('///')):
        initial_slashes = 2
317
    comps = path.split('/')
318 319 320 321
    new_comps = []
    for comp in comps:
        if comp in ('', '.'):
            continue
322
        if (comp != '..' or (not initial_slashes and not new_comps) or
323 324 325 326 327
             (new_comps and new_comps[-1] == '..')):
            new_comps.append(comp)
        elif new_comps:
            new_comps.pop()
    comps = new_comps
328
    path = '/'.join(comps)
329 330
    if initial_slashes:
        path = '/'*initial_slashes + path
331
    return path or '.'
Guido van Rossum's avatar
Guido van Rossum committed
332 333 334


def abspath(path):
335
    """Return an absolute path."""
Guido van Rossum's avatar
Guido van Rossum committed
336 337 338
    if not isabs(path):
        path = join(os.getcwd(), path)
    return normpath(path)
339 340 341 342 343 344 345 346


# Return a canonical path (i.e. the absolute location of a file on the
# filesystem).

def realpath(filename):
    """Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
347 348 349
    if isabs(filename):
        bits = ['/'] + filename.split('/')[1:]
    else:
350
        bits = [''] + filename.split('/')
Tim Peters's avatar
Tim Peters committed
351

352 353
    for i in range(2, len(bits)+1):
        component = join(*bits[0:i])
354
        # Resolve symbolic links.
355
        if islink(component):
356 357 358
            resolved = _resolve_link(component)
            if resolved is None:
                # Infinite loop -- return original component + rest of the path
359
                return abspath(join(*([component] + bits[i:])))
360 361
            else:
                newpath = join(*([resolved] + bits[i:]))
Tim Peters's avatar
Tim Peters committed
362
                return realpath(newpath)
363

364
    return abspath(filename)
Tim Peters's avatar
Tim Peters committed
365

366 367 368

def _resolve_link(path):
    """Internal helper function.  Takes a path and follows symlinks
369
    until we either arrive at something that isn't a symlink, or
370 371 372 373
    encounter a path we've seen before (meaning that there's a loop).
    """
    paths_seen = []
    while islink(path):
374
        if path in paths_seen:
375 376
            # Already seen this path, so we must have a symlink loop
            return None
377
        paths_seen.append(path)
378
        # Resolve where the link points to
379
        resolved = os.readlink(path)
380
        if not isabs(resolved):
381 382 383 384 385 386
            dir = dirname(path)
            path = normpath(join(dir, resolved))
        else:
            path = normpath(resolved)
    return path

387
supports_unicode_filenames = False
388 389 390 391 392 393

def relpath(path, start=curdir):
    """Return a relative version of a path"""

    if not path:
        raise ValueError("no path specified")
394

395 396
    start_list = abspath(start).split(sep)
    path_list = abspath(path).split(sep)
397

398 399 400 401
    # Work out how much of the filepath is shared by start and path.
    i = len(commonprefix([start_list, path_list]))

    rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
402 403
    if not rel_list:
        return curdir
404
    return join(*rel_list)