[Python-ideas] Adding optional parameter to shutil.rmtree to not delete root.

eryk sun eryksun at gmail.com
Thu Aug 25 00:48:49 EDT 2016


On Thu, Aug 25, 2016 at 2:29 AM, Nick Jacobson via Python-ideas
<python-ideas at python.org> wrote:
> I've been finding that a common scenario is where I want to remove
> everything in a directory, but leave the (empty) root directory behind, not
> removing it.
>
> So for example, if I have a directory C:\foo and it contains subdirectory
> C:\foo\bar and file C:\foo\myfile.txt, and I want to remove the subdirectory
> (and everything in it) and file, leaving only C:\foo behind.
>
> (This is useful e.g. when the root directory has special permissions, so it
> wouldn't be so simple to remove it and recreate it again.)

Here's a Windows workaround that clears the delete disposition after
rmtree 'deletes' the directory. A Windows file or directory absolutely
cannot be unlinked while there are handle or kernel references to it,
and a handle with DELETE access can set and unset the delete
disposition. This used to require the system call
NtSetInformationFile, but Vista added SetFileInformationByHandle to
the Windows API.

    import contextlib
    import ctypes
    import _winapi

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    kernel32.SetFileInformationByHandle # Vista minimum (NT 6.0+)

    DELETE = 0x00010000
    SHARE_ALL = 7
    OPEN_EXISTING = 3
    BACKUP = 0x02000000
    FileDispositionInfo = 4

    @contextlib.contextmanager
    def protect_file(path):
        hFile = _winapi.CreateFile(path, DELETE, SHARE_ALL, 0,
                                   OPEN_EXISTING, BACKUP, 0)
        try:
            yield
            if not kernel32.SetFileInformationByHandle(
                        hFile, FileDispositionInfo,
                        (ctypes.c_ulong * 1)(0), 4):
                raise ctypes.WinError(ctypes.get_last_error())
        finally:
            kernel32.CloseHandle(hFile)

For example:

    >>> os.listdir('test')
    ['dir1', 'dir2', 'file']
    >>> with protect_file('test'):
    ...     shutil.rmtree('test')
    ...
    >>> os.listdir('test')
    []

Another example:

    >>> open('file', 'w').close()
    >>> with protect_file('file'):
    ...     os.remove('file')
    ...
    >>> os.path.exists('file')
    True


More information about the Python-ideas mailing list