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. Mac, 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. macpath, 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 | import os
|
---|
14 | import stat
|
---|
15 | import genericpath
|
---|
16 | import warnings
|
---|
17 | from genericpath import *
|
---|
18 |
|
---|
19 | __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
|
---|
20 | "basename","dirname","commonprefix","getsize","getmtime",
|
---|
21 | "getatime","getctime","islink","exists","lexists","isdir","isfile",
|
---|
22 | "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"]
|
---|
26 |
|
---|
27 | # strings representing various path-related bits and pieces
|
---|
28 | curdir = '.'
|
---|
29 | pardir = '..'
|
---|
30 | extsep = '.'
|
---|
31 | sep = '/'
|
---|
32 | pathsep = ':'
|
---|
33 | defpath = ':/bin:/usr/bin'
|
---|
34 | altsep = None
|
---|
35 | devnull = '/dev/null'
|
---|
36 |
|
---|
37 | # Normalize the case of a pathname. Trivial in Posix, string.lower on Mac.
|
---|
38 | # On MS-DOS this may also turn slashes into backslashes; however, other
|
---|
39 | # normalizations (such as optimizing '../' away) are not allowed
|
---|
40 | # (another function should be defined to do that).
|
---|
41 |
|
---|
42 | def normcase(s):
|
---|
43 | """Normalize case of pathname. Has no effect under Posix"""
|
---|
44 | return s
|
---|
45 |
|
---|
46 |
|
---|
47 | # Return whether a path is absolute.
|
---|
48 | # Trivial in Posix, harder on the Mac or MS-DOS.
|
---|
49 |
|
---|
50 | def isabs(s):
|
---|
51 | """Test whether a path is absolute"""
|
---|
52 | return s.startswith('/')
|
---|
53 |
|
---|
54 |
|
---|
55 | # Join pathnames.
|
---|
56 | # Ignore the previous parts if a part is absolute.
|
---|
57 | # Insert a '/' unless the first part is empty or already ends in '/'.
|
---|
58 |
|
---|
59 | def join(a, *p):
|
---|
60 | """Join two or more pathname components, inserting '/' as needed.
|
---|
61 | If any component is an absolute path, all previous path components
|
---|
62 | will be discarded."""
|
---|
63 | path = a
|
---|
64 | for b in p:
|
---|
65 | if b.startswith('/'):
|
---|
66 | path = b
|
---|
67 | elif path == '' or path.endswith('/'):
|
---|
68 | path += b
|
---|
69 | else:
|
---|
70 | path += '/' + b
|
---|
71 | return path
|
---|
72 |
|
---|
73 |
|
---|
74 | # Split a path in head (everything up to the last '/') and tail (the
|
---|
75 | # rest). If the path ends in '/', tail will be empty. If there is no
|
---|
76 | # '/' in the path, head will be empty.
|
---|
77 | # Trailing '/'es are stripped from head unless it is the root.
|
---|
78 |
|
---|
79 | def split(p):
|
---|
80 | """Split a pathname. Returns tuple "(head, tail)" where "tail" is
|
---|
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
|
---|
87 |
|
---|
88 |
|
---|
89 | # Split a path in root and extension.
|
---|
90 | # The extension is everything starting at the last dot in the last
|
---|
91 | # pathname component; the root is everything before that.
|
---|
92 | # It is always true that root + ext == p.
|
---|
93 |
|
---|
94 | def splitext(p):
|
---|
95 | return genericpath._splitext(p, sep, altsep, extsep)
|
---|
96 | splitext.__doc__ = genericpath._splitext.__doc__
|
---|
97 |
|
---|
98 | # Split a pathname into a drive specification and the rest of the
|
---|
99 | # path. Useful on DOS/Windows/NT; on Unix, the drive is always empty.
|
---|
100 |
|
---|
101 | def splitdrive(p):
|
---|
102 | """Split a pathname into drive and path. On Posix, drive is always
|
---|
103 | empty."""
|
---|
104 | return '', p
|
---|
105 |
|
---|
106 |
|
---|
107 | # Return the tail (basename) part of a path, same as split(path)[1].
|
---|
108 |
|
---|
109 | def basename(p):
|
---|
110 | """Returns the final component of a pathname"""
|
---|
111 | i = p.rfind('/') + 1
|
---|
112 | return p[i:]
|
---|
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 | i = p.rfind('/') + 1
|
---|
120 | head = p[:i]
|
---|
121 | if head and head != '/'*len(head):
|
---|
122 | head = head.rstrip('/')
|
---|
123 | return head
|
---|
124 |
|
---|
125 |
|
---|
126 | # Is a path a symbolic link?
|
---|
127 | # This will always return false on systems where os.lstat doesn't exist.
|
---|
128 |
|
---|
129 | def islink(path):
|
---|
130 | """Test whether a path is a symbolic link"""
|
---|
131 | try:
|
---|
132 | st = os.lstat(path)
|
---|
133 | except (os.error, AttributeError):
|
---|
134 | return False
|
---|
135 | return stat.S_ISLNK(st.st_mode)
|
---|
136 |
|
---|
137 | # Being true for dangling symbolic links is also useful.
|
---|
138 |
|
---|
139 | def lexists(path):
|
---|
140 | """Test whether a path exists. Returns True for broken symbolic links"""
|
---|
141 | try:
|
---|
142 | st = os.lstat(path)
|
---|
143 | except os.error:
|
---|
144 | return False
|
---|
145 | return True
|
---|
146 |
|
---|
147 |
|
---|
148 | # Are two filenames really pointing to the same file?
|
---|
149 |
|
---|
150 | def samefile(f1, f2):
|
---|
151 | """Test whether two pathnames reference the same actual file"""
|
---|
152 | s1 = os.stat(f1)
|
---|
153 | s2 = os.stat(f2)
|
---|
154 | return samestat(s1, s2)
|
---|
155 |
|
---|
156 |
|
---|
157 | # Are two open files really referencing the same file?
|
---|
158 | # (Not necessarily the same file descriptor!)
|
---|
159 |
|
---|
160 | def sameopenfile(fp1, fp2):
|
---|
161 | """Test whether two open file objects reference the same file"""
|
---|
162 | s1 = os.fstat(fp1)
|
---|
163 | s2 = os.fstat(fp2)
|
---|
164 | return samestat(s1, s2)
|
---|
165 |
|
---|
166 |
|
---|
167 | # Are two stat buffers (obtained from stat, fstat or lstat)
|
---|
168 | # describing the same file?
|
---|
169 |
|
---|
170 | def samestat(s1, s2):
|
---|
171 | """Test whether two stat buffers reference the same file"""
|
---|
172 | return s1.st_ino == s2.st_ino and \
|
---|
173 | s1.st_dev == s2.st_dev
|
---|
174 |
|
---|
175 |
|
---|
176 | # Is a path a mount point?
|
---|
177 | # (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
|
---|
178 |
|
---|
179 | def ismount(path):
|
---|
180 | """Test whether a path is a mount point"""
|
---|
181 | try:
|
---|
182 | s1 = os.lstat(path)
|
---|
183 | s2 = os.lstat(join(path, '..'))
|
---|
184 | except os.error:
|
---|
185 | return False # It doesn't exist -- so not a mount point :-)
|
---|
186 | dev1 = s1.st_dev
|
---|
187 | dev2 = s2.st_dev
|
---|
188 | if dev1 != dev2:
|
---|
189 | return True # path/.. on a different device as path
|
---|
190 | ino1 = s1.st_ino
|
---|
191 | ino2 = s2.st_ino
|
---|
192 | if ino1 == ino2:
|
---|
193 | return True # path/.. is the same i-node as path
|
---|
194 | return False
|
---|
195 |
|
---|
196 |
|
---|
197 | # Directory tree walk.
|
---|
198 | # For each directory under top (including top itself, but excluding
|
---|
199 | # '.' and '..'), func(arg, dirname, filenames) is called, where
|
---|
200 | # dirname is the name of the directory and filenames is the list
|
---|
201 | # of files (and subdirectories etc.) in the directory.
|
---|
202 | # The func may modify the filenames list, to implement a filter,
|
---|
203 | # or to impose a different order of visiting.
|
---|
204 |
|
---|
205 | def walk(top, func, arg):
|
---|
206 | """Directory tree walk with callback function.
|
---|
207 |
|
---|
208 | For each directory in the directory tree rooted at top (including top
|
---|
209 | itself, but excluding '.' and '..'), call func(arg, dirname, fnames).
|
---|
210 | dirname is the name of the directory, and fnames a list of the names of
|
---|
211 | the files and subdirectories in dirname (excluding '.' and '..'). func
|
---|
212 | may modify the fnames list in-place (e.g. via del or slice assignment),
|
---|
213 | and walk will only recurse into the subdirectories whose names remain in
|
---|
214 | fnames; this can be used to implement a filter, or to impose a specific
|
---|
215 | order of visiting. No semantics are defined for, or required of, arg,
|
---|
216 | beyond that arg is always passed to func. It can be used, e.g., to pass
|
---|
217 | a filename pattern, or a mutable object designed to accumulate
|
---|
218 | statistics. Passing None for arg is common."""
|
---|
219 | warnings.warnpy3k("In 3.x, os.path.walk is removed in favor of os.walk.",
|
---|
220 | stacklevel=2)
|
---|
221 | try:
|
---|
222 | names = os.listdir(top)
|
---|
223 | except os.error:
|
---|
224 | return
|
---|
225 | func(arg, top, names)
|
---|
226 | for name in names:
|
---|
227 | name = join(top, name)
|
---|
228 | try:
|
---|
229 | st = os.lstat(name)
|
---|
230 | except os.error:
|
---|
231 | continue
|
---|
232 | if stat.S_ISDIR(st.st_mode):
|
---|
233 | walk(name, func, arg)
|
---|
234 |
|
---|
235 |
|
---|
236 | # Expand paths beginning with '~' or '~user'.
|
---|
237 | # '~' means $HOME; '~user' means that user's home directory.
|
---|
238 | # If the path doesn't begin with '~', or if the user or $HOME is unknown,
|
---|
239 | # the path is returned unchanged (leaving error reporting to whatever
|
---|
240 | # function is called with the expanded path as argument).
|
---|
241 | # See also module 'glob' for expansion of *, ? and [...] in pathnames.
|
---|
242 | # (A function should also be defined to do full *sh-style environment
|
---|
243 | # variable expansion.)
|
---|
244 |
|
---|
245 | def expanduser(path):
|
---|
246 | """Expand ~ and ~user constructions. If user or $HOME is unknown,
|
---|
247 | do nothing."""
|
---|
248 | if not path.startswith('~'):
|
---|
249 | return path
|
---|
250 | i = path.find('/', 1)
|
---|
251 | if i < 0:
|
---|
252 | i = len(path)
|
---|
253 | if i == 1:
|
---|
254 | if 'HOME' not in os.environ:
|
---|
255 | import pwd
|
---|
256 | userhome = pwd.getpwuid(os.getuid()).pw_dir
|
---|
257 | else:
|
---|
258 | userhome = os.environ['HOME']
|
---|
259 | else:
|
---|
260 | import pwd
|
---|
261 | try:
|
---|
262 | pwent = pwd.getpwnam(path[1:i])
|
---|
263 | except KeyError:
|
---|
264 | return path
|
---|
265 | userhome = pwent.pw_dir
|
---|
266 | userhome = userhome.rstrip('/') or userhome
|
---|
267 | return userhome + path[i:]
|
---|
268 |
|
---|
269 |
|
---|
270 | # Expand paths containing shell variable substitutions.
|
---|
271 | # This expands the forms $variable and ${variable} only.
|
---|
272 | # Non-existent variables are left unchanged.
|
---|
273 |
|
---|
274 | _varprog = None
|
---|
275 |
|
---|
276 | def expandvars(path):
|
---|
277 | """Expand shell variables of form $var and ${var}. Unknown variables
|
---|
278 | are left unchanged."""
|
---|
279 | global _varprog
|
---|
280 | if '$' not in path:
|
---|
281 | return path
|
---|
282 | if not _varprog:
|
---|
283 | import re
|
---|
284 | _varprog = re.compile(r'\$(\w+|\{[^}]*\})')
|
---|
285 | i = 0
|
---|
286 | while True:
|
---|
287 | m = _varprog.search(path, i)
|
---|
288 | if not m:
|
---|
289 | break
|
---|
290 | i, j = m.span(0)
|
---|
291 | name = m.group(1)
|
---|
292 | if name.startswith('{') and name.endswith('}'):
|
---|
293 | name = name[1:-1]
|
---|
294 | if name in os.environ:
|
---|
295 | tail = path[j:]
|
---|
296 | path = path[:i] + os.environ[name]
|
---|
297 | i = len(path)
|
---|
298 | path += tail
|
---|
299 | else:
|
---|
300 | i = j
|
---|
301 | return path
|
---|
302 |
|
---|
303 |
|
---|
304 | # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
|
---|
305 | # It should be understood that this may change the meaning of the path
|
---|
306 | # if it contains symbolic links!
|
---|
307 |
|
---|
308 | def normpath(path):
|
---|
309 | """Normalize path, eliminating double slashes, etc."""
|
---|
310 | # Preserve unicode (if path is unicode)
|
---|
311 | slash, dot = (u'/', u'.') if isinstance(path, unicode) else ('/', '.')
|
---|
312 | if path == '':
|
---|
313 | return dot
|
---|
314 | initial_slashes = path.startswith('/')
|
---|
315 | # POSIX allows one or two initial slashes, but treats three or more
|
---|
316 | # as single slash.
|
---|
317 | if (initial_slashes and
|
---|
318 | path.startswith('//') and not path.startswith('///')):
|
---|
319 | initial_slashes = 2
|
---|
320 | comps = path.split('/')
|
---|
321 | new_comps = []
|
---|
322 | for comp in comps:
|
---|
323 | if comp in ('', '.'):
|
---|
324 | continue
|
---|
325 | if (comp != '..' or (not initial_slashes and not new_comps) or
|
---|
326 | (new_comps and new_comps[-1] == '..')):
|
---|
327 | new_comps.append(comp)
|
---|
328 | elif new_comps:
|
---|
329 | new_comps.pop()
|
---|
330 | comps = new_comps
|
---|
331 | path = slash.join(comps)
|
---|
332 | if initial_slashes:
|
---|
333 | path = slash*initial_slashes + path
|
---|
334 | return path or dot
|
---|
335 |
|
---|
336 |
|
---|
337 | def abspath(path):
|
---|
338 | """Return an absolute path."""
|
---|
339 | if not isabs(path):
|
---|
340 | if isinstance(path, unicode):
|
---|
341 | cwd = os.getcwdu()
|
---|
342 | else:
|
---|
343 | cwd = os.getcwd()
|
---|
344 | path = join(cwd, path)
|
---|
345 | return normpath(path)
|
---|
346 |
|
---|
347 |
|
---|
348 | # Return a canonical path (i.e. the absolute location of a file on the
|
---|
349 | # filesystem).
|
---|
350 |
|
---|
351 | def realpath(filename):
|
---|
352 | """Return the canonical path of the specified filename, eliminating any
|
---|
353 | symbolic links encountered in the path."""
|
---|
354 | if isabs(filename):
|
---|
355 | bits = ['/'] + filename.split('/')[1:]
|
---|
356 | else:
|
---|
357 | bits = [''] + filename.split('/')
|
---|
358 |
|
---|
359 | for i in range(2, len(bits)+1):
|
---|
360 | component = join(*bits[0:i])
|
---|
361 | # Resolve symbolic links.
|
---|
362 | if islink(component):
|
---|
363 | resolved = _resolve_link(component)
|
---|
364 | if resolved is None:
|
---|
365 | # Infinite loop -- return original component + rest of the path
|
---|
366 | return abspath(join(*([component] + bits[i:])))
|
---|
367 | else:
|
---|
368 | newpath = join(*([resolved] + bits[i:]))
|
---|
369 | return realpath(newpath)
|
---|
370 |
|
---|
371 | return abspath(filename)
|
---|
372 |
|
---|
373 |
|
---|
374 | def _resolve_link(path):
|
---|
375 | """Internal helper function. Takes a path and follows symlinks
|
---|
376 | until we either arrive at something that isn't a symlink, or
|
---|
377 | encounter a path we've seen before (meaning that there's a loop).
|
---|
378 | """
|
---|
379 | paths_seen = []
|
---|
380 | while islink(path):
|
---|
381 | if path in paths_seen:
|
---|
382 | # Already seen this path, so we must have a symlink loop
|
---|
383 | return None
|
---|
384 | paths_seen.append(path)
|
---|
385 | # Resolve where the link points to
|
---|
386 | resolved = os.readlink(path)
|
---|
387 | if not isabs(resolved):
|
---|
388 | dir = dirname(path)
|
---|
389 | path = normpath(join(dir, resolved))
|
---|
390 | else:
|
---|
391 | path = normpath(resolved)
|
---|
392 | return path
|
---|
393 |
|
---|
394 | supports_unicode_filenames = False
|
---|
395 |
|
---|
396 | def relpath(path, start=curdir):
|
---|
397 | """Return a relative version of a path"""
|
---|
398 |
|
---|
399 | if not path:
|
---|
400 | raise ValueError("no path specified")
|
---|
401 |
|
---|
402 | start_list = abspath(start).split(sep)
|
---|
403 | path_list = abspath(path).split(sep)
|
---|
404 |
|
---|
405 | # Work out how much of the filepath is shared by start and path.
|
---|
406 | i = len(commonprefix([start_list, path_list]))
|
---|
407 |
|
---|
408 | rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
|
---|
409 | if not rel_list:
|
---|
410 | return curdir
|
---|
411 | return join(*rel_list)
|
---|