shutil模塊
-- --High-level file operations 高級的文件操作模塊。
os模塊提供了對目錄或者文件的新建/刪除/查看文件屬性,還提供了對文件以及目錄的路徑操作。比如說:絕對路徑,父目錄…… 但是,os文件的操作還應該包含移動 復制 打包 壓縮 解壓等操作,這些os模塊都沒有提供。
而本章所講的shutil則就是對os中文件操作的補充。--移動 復制 打包 壓縮 解壓,
shutil功能:
1 shutil.copyfileobj(fsrc, fdst[, length=16*1024]) #copy文件內容到另一個文件,可以copy指定大小的內容

#先來看看其源代碼。 def copyfileobj(fsrc, fdst, length=16*1024): """copy data from file-like object fsrc to file-like object fdst""" while 1: buf = fsrc.read(length) if not buf: break fdst.write(buf) #注意! 在其中fsrc,fdst都是文件對象,都需要打開后才能進行復制操作 import shutil f1=open('name','r') f2=open('name_copy','w+') shutil.copyfileobj(f1,f2,length=16*1024)
2 shutil.copyfile(src,dst) #copy文件內容,是不是感覺上面的文件復制很麻煩?還需要自己手動用open函數打開文件,在這里就不需要了,事實上,copyfile調用了copyfileobj

def copyfile(src, dst, *, follow_symlinks=True): """Copy data from src to dst. If follow_symlinks is not set and src is a symbolic link, a new symlink will be created instead of copying the file it points to. """ if _samefile(src, dst): raise SameFileError("{!r} and {!r} are the same file".format(src, dst)) for fn in [src, dst]: try: st = os.stat(fn) except OSError: # File most likely does not exist pass else: # XXX What about other special files? (sockets, devices...) if stat.S_ISFIFO(st.st_mode): raise SpecialFileError("`%s` is a named pipe" % fn) if not follow_symlinks and os.path.islink(src): os.symlink(os.readlink(src), dst) else: with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: copyfileobj(fsrc, fdst) return dst 查看源代碼

shutil.copyfile('name','name_copy_2') #一句就可以實現復制文件內容
3 shutil.copymode(src,dst) #僅copy權限,不更改文件內容,組和用戶。

def copymode(src, dst, *, follow_symlinks=True): """Copy mode bits from src to dst. If follow_symlinks is not set, symlinks aren't followed if and only if both `src` and `dst` are symlinks. If `lchmod` isn't available (e.g. Linux) this method does nothing. """ if not follow_symlinks and os.path.islink(src) and os.path.islink(dst): if hasattr(os, 'lchmod'): stat_func, chmod_func = os.lstat, os.lchmod else: return elif hasattr(os, 'chmod'): stat_func, chmod_func = os.stat, os.chmod else: return st = stat_func(src) chmod_func(dst, stat.S_IMODE(st.st_mode)) 查看源代碼

#先看兩個文件的權限 [root@slyoyo python_test]# ls -l total 4 -rw-r--r--. 1 root root 79 May 14 05:17 test1 -rwxr-xr-x. 1 root root 0 May 14 19:10 test2 #運行命令 >>> import shutil >>> shutil.copymode('test1','test2') #查看結果 [root@slyoyo python_test]# ls -l total 4 -rw-r--r--. 1 root root 79 May 14 05:17 test1 -rw-r--r--. 1 root root 0 May 14 19:10 test2 #當我們將目標文件換為一個不存在的文件時報錯 >>> shutil.copymode('test1','test3') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/python/lib/python3.4/shutil.py", line 132, in copymode chmod_func(dst, stat.S_IMODE(st.st_mode)) FileNotFoundError: [Errno 2] No such file or directory: 'test233'
4 shutil.copystat(src,dst) #復制所有的狀態信息,包括權限,組,用戶,時間等

def copystat(src, dst, *, follow_symlinks=True): """Copy all stat info (mode bits, atime, mtime, flags) from src to dst. If the optional flag `follow_symlinks` is not set, symlinks aren't followed if and only if both `src` and `dst` are symlinks. """ def _nop(*args, ns=None, follow_symlinks=None): pass # follow symlinks (aka don't not follow symlinks) follow = follow_symlinks or not (os.path.islink(src) and os.path.islink(dst)) if follow: # use the real function if it exists def lookup(name): return getattr(os, name, _nop) else: # use the real function only if it exists # *and* it supports follow_symlinks def lookup(name): fn = getattr(os, name, _nop) if fn in os.supports_follow_symlinks: return fn return _nop st = lookup("stat")(src, follow_symlinks=follow) mode = stat.S_IMODE(st.st_mode) lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), follow_symlinks=follow) try: lookup("chmod")(dst, mode, follow_symlinks=follow) except NotImplementedError: # if we got a NotImplementedError, it's because # * follow_symlinks=False, # * lchown() is unavailable, and # * either # * fchownat() is unavailable or # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW. # (it returned ENOSUP.) # therefore we're out of options--we simply cannot chown the # symlink. give up, suppress the error. # (which is what shutil always did in this circumstance.) pass if hasattr(st, 'st_flags'): try: lookup("chflags")(dst, st.st_flags, follow_symlinks=follow) except OSError as why: for err in 'EOPNOTSUPP', 'ENOTSUP': if hasattr(errno, err) and why.errno == getattr(errno, err): break else: raise _copyxattr(src, dst, follow_symlinks=follow)
5 shutil.copy(src,dst) #復制文件的內容以及權限,先copyfile后copymode

def copy(src, dst, *, follow_symlinks=True): """Copy data and mode bits ("cp src dst"). Return the file's destination. The destination may be a directory. If follow_symlinks is false, symlinks won't be followed. This resembles GNU's "cp -P src dst". If source and destination are the same file, a SameFileError will be raised. """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) copyfile(src, dst, follow_symlinks=follow_symlinks) copymode(src, dst, follow_symlinks=follow_symlinks) return dst
6 shutil.copy2(src,dst) #復制文件的內容以及文件的所有狀態信息。先copyfile后copystat

def copy2(src, dst, *, follow_symlinks=True): """Copy data and all stat info ("cp -p src dst"). Return the file's destination." The destination may be a directory. If follow_symlinks is false, symlinks won't be followed. This resembles GNU's "cp -P src dst". """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) copyfile(src, dst, follow_symlinks=follow_symlinks) copystat(src, dst, follow_symlinks=follow_symlinks) return dst
7 shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,ignore_dangling_symlinks=False) #遞歸的復制文件內容及狀態信息

def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False): """Recursively copy a directory tree. The destination directory must not already exist. If exception(s) occur, an Error is raised with a list of reasons. If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic links are copied. If the file pointed by the symlink doesn't exist, an exception will be added in the list of errors raised in an Error exception at the end of the copy process. You can set the optional ignore_dangling_symlinks flag to true if you want to silence this exception. Notice that this has no effect on platforms that don't support os.symlink. The optional ignore argument is a callable. If given, it is called with the `src` parameter, which is the directory being visited by copytree(), and `names` which is the list of `src` contents, as returned by os.listdir(): callable(src, names) -> ignored_names Since copytree() is called recursively, the callable will be called once for each directory that is copied. It returns a list of names relative to the `src` directory that should not be copied. The optional copy_function argument is a callable that will be used to copy each file. It will be called with the source path and the destination path as arguments. By default, copy2() is used, but any function that supports the same signature (like copy()) can be used. """ names = os.listdir(src) if ignore is not None: ignored_names = ignore(src, names) else: ignored_names = set() os.makedirs(dst) errors = [] for name in names: if name in ignored_names: continue srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: if os.path.islink(srcname): linkto = os.readlink(srcname) if symlinks: # We can't just leave it to `copy_function` because legacy # code with a custom `copy_function` may rely on copytree # doing the right thing. os.symlink(linkto, dstname) copystat(srcname, dstname, follow_symlinks=not symlinks) else: # ignore dangling symlink if the flag is on if not os.path.exists(linkto) and ignore_dangling_symlinks: continue # otherwise let the copy occurs. copy2 will raise an error if os.path.isdir(srcname): copytree(srcname, dstname, symlinks, ignore, copy_function) else: copy_function(srcname, dstname) elif os.path.isdir(srcname): copytree(srcname, dstname, symlinks, ignore, copy_function) else: # Will raise a SpecialFileError for unsupported file types copy_function(srcname, dstname) # catch the Error from the recursive copytree so that we can # continue with other files except Error as err: errors.extend(err.args[0]) except OSError as why: errors.append((srcname, dstname, str(why))) try: copystat(src, dst) except OSError as why: # Copying file access times may fail on Windows if getattr(why, 'winerror', None) is None: errors.append((src, dst, str(why))) if errors: raise Error(errors) return dst # version vulnerable to race conditions

[root@slyoyo python_test]# tree copytree_test/ copytree_test/ └── test ├── test1 ├── test2 └── hahaha [root@slyoyo test]# ls -l total 0 -rw-r--r--. 1 python python 0 May 14 19:36 hahaha -rw-r--r--. 1 python python 0 May 14 19:36 test1 -rw-r--r--. 1 root root 0 May 14 19:36 test2 >>> shutil.copytree('copytree_test','copytree_copy') 'copytree_copy' [root@slyoyo python_test]# ls -l total 12 drwxr-xr-x. 3 root root 4096 May 14 19:36 copytree_copy drwxr-xr-x. 3 root root 4096 May 14 19:36 copytree_test -rw-r--r--. 1 python python 79 May 14 05:17 test1 -rw-r--r--. 1 root root 0 May 14 19:10 test2 [root@slyoyo python_test]# tree copytree_copy/ copytree_copy/ └── test ├── hahaha ├── test1 └── test2
8 shutil.rmtree(path, ignore_errors=False, onerror=None) #遞歸地刪除文件

def rmtree(path, ignore_errors=False, onerror=None): """Recursively delete a directory tree. If ignore_errors is set, errors are ignored; otherwise, if onerror is set, it is called to handle the error with arguments (func, path, exc_info) where func is platform and implementation dependent; path is the argument to that function that caused it to fail; and exc_info is a tuple returned by sys.exc_info(). If ignore_errors is false and onerror is None, an exception is raised. """ if ignore_errors: def onerror(*args): pass elif onerror is None: def onerror(*args): raise if _use_fd_functions: # While the unsafe rmtree works fine on bytes, the fd based does not. if isinstance(path, bytes): path = os.fsdecode(path) # Note: To guard against symlink races, we use the standard # lstat()/open()/fstat() trick. try: orig_st = os.lstat(path) except Exception: onerror(os.lstat, path, sys.exc_info()) return try: fd = os.open(path, os.O_RDONLY) except Exception: onerror(os.lstat, path, sys.exc_info()) return try: if os.path.samestat(orig_st, os.fstat(fd)): _rmtree_safe_fd(fd, path, onerror) try: os.rmdir(path) except OSError: onerror(os.rmdir, path, sys.exc_info()) else: try: # symlinks to directories are forbidden, see bug #1669 raise OSError("Cannot call rmtree on a symbolic link") except OSError: onerror(os.path.islink, path, sys.exc_info()) finally: os.close(fd) else: return _rmtree_unsafe(path, onerror) # Allow introspection of whether or not the hardening against symlink # attacks is supported on the current platform rmtree.avoids_symlink_attacks = _use_fd_functions
9 shutil.move(src, dst) #遞歸的移動文件

def move(src, dst): """Recursively move a file or directory to another location. This is similar to the Unix "mv" command. Return the file or directory's destination. If the destination is a directory or a symlink to a directory, the source is moved inside the directory. The destination path must not already exist. If the destination already exists but is not a directory, it may be overwritten depending on os.rename() semantics. If the destination is on our current filesystem, then rename() is used. Otherwise, src is copied to the destination and then removed. Symlinks are recreated under the new name if os.rename() fails because of cross filesystem renames. A lot more could be done here... A look at a mv.c shows a lot of the issues this implementation glosses over. """ real_dst = dst if os.path.isdir(dst): if _samefile(src, dst): # We might be on a case insensitive filesystem, # perform the rename anyway. os.rename(src, dst) return real_dst = os.path.join(dst, _basename(src)) if os.path.exists(real_dst): raise Error("Destination path '%s' already exists" % real_dst) try: os.rename(src, real_dst) except OSError: if os.path.islink(src): linkto = os.readlink(src) os.symlink(linkto, real_dst) os.unlink(src) elif os.path.isdir(src): if _destinsrc(src, dst): raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst)) copytree(src, real_dst, symlinks=True) rmtree(src) else: copy2(src, real_dst) os.unlink(src) return real_dst
10 make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,dry_run=0, owner=None, group=None, logger=None) #壓縮打包

def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, dry_run=0, owner=None, group=None, logger=None): """Create an archive file (eg. zip or tar). 'base_name' is the name of the file to create, minus any format-specific extension; 'format' is the archive format: one of "zip", "tar", "bztar" or "gztar". 'root_dir' is a directory that will be the root directory of the archive; ie. we typically chdir into 'root_dir' before creating the archive. 'base_dir' is the directory where we start archiving from; ie. 'base_dir' will be the common prefix of all files and directories in the archive. 'root_dir' and 'base_dir' both default to the current directory. Returns the name of the archive file. 'owner' and 'group' are used when creating a tar archive. By default, uses the current owner and group. """ save_cwd = os.getcwd() if root_dir is not None: if logger is not None: logger.debug("changing into '%s'", root_dir) base_name = os.path.abspath(base_name) if not dry_run: os.chdir(root_dir) if base_dir is None: base_dir = os.curdir kwargs = {'dry_run': dry_run, 'logger': logger} try: format_info = _ARCHIVE_FORMATS[format] except KeyError: raise ValueError("unknown archive format '%s'" % format) func = format_info[0] for arg, val in format_info[1]: kwargs[arg] = val if format != 'zip': kwargs['owner'] = owner kwargs['group'] = group try: filename = func(base_name, base_dir, **kwargs) finally: if root_dir is not None: if logger is not None: logger.debug("changing back to '%s'", save_cwd) os.chdir(save_cwd) return filename
base_name: 壓縮打包后的文件名或者路徑名
format: 壓縮或者打包格式 "zip", "tar", "bztar"or "gztar"
root_dir : 將哪個目錄或者文件打包(也就是源文件)

>>> shutil.make_archive('tarball','gztar',root_dir='copytree_test') [root@slyoyo python_test]# ls -l total 12 drwxr-xr-x. 3 root root 4096 May 14 19:36 copytree_copy drwxr-xr-x. 3 root root 4096 May 14 19:36 copytree_test -rw-r--r--. 1 root root 0 May 14 21:12 tarball.tar.gz -rw-r--r--. 1 python python 79 May 14 05:17 test1 -rw-r--r--. 1 root root 0 May 14 19:10 test2