sriram
sriram

Reputation: 9022

Deleting folders in python recursively

I'm having a problem with deleting empty directories. Here is my code:

for dirpath, dirnames, filenames in os.walk(dir_to_search):
    # other codes

    try:
        os.rmdir(dirpath)
    except OSError as ex:
        print(ex)

The argument dir_to_search is where I'm passing the directory where the work needs to be done. That directory looks like this:

test/20/...
test/22/...
test/25/...
test/26/...

Note that all the above folders are empty. When I run this script the folders 20,25 alone gets deleted! But the folders 25 and 26 aren't deleted, even though they are empty folders.

Edit:

The exception that I'm getting are:

[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/29'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/29/tmp'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/28'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/28/tmp'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/26'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/25'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/27'
[Errno 39] Directory not empty: '/home/python-user/shell-scripts/s3logs/test/2012/10/27/tmp'

Where am I making a mistake?

Upvotes: 355

Views: 353221

Answers (14)

Tomek
Tomek

Reputation: 7444

Try shutil.rmtree to delete files and directories:

import shutil
shutil.rmtree('/path/to/your/dir/')

Upvotes: 732

hlongmore
hlongmore

Reputation: 1846

I like the answers by @buhtz and @pepoluan the best for their use of pathlib.Path and an iterative solution instead of recursion. However, they can be problematic if there are multiple levels of directories. All of the files may be deleted, but then the loop will start on the what may be the top level non-empty directory that contains only sub-directories, no files, and fail.

In Python 3.12, a walk method has been added to Path that lets one walk bottom-up to avoid this problem. The sample code implementing shutil.rmtree from the documentation is repeated here for convenience:

# Delete everything reachable from the directory "top".
# CAUTION:  This is dangerous! For example, if top == Path('/'),
# it could delete all of your files.
for root, dirs, files in top.walk(top_down=False):
    for name in files:
        (root / name).unlink()
    for name in dirs:
        (root / name).rmdir()

Note that as the original question asked about deleting empty directories, this code will also delete files (see also the comment by @DaveSawyer on the accepted answer; and, note that shutil.rmtree will also delete files beneath the specified root). If you only want to delete empty directories, remove the for name in files: block and handle the exceptions for non-empty directories if desired.

If, on the other hand, you want to handle files or directories that may be read-only, as done by @acapola, @monir, and @pepoluan you can do so in either for loop.

If you aren't in a position to upgrade to 3.12, you may still be able to utilize os.walk and pathlib.Path:

for root, dirs, files in os.walk(dir_to_search, topdown=False):
    root_path = Path(root)
    for name in files:
        (root_path / name).unlink()
    for name in dirs:
        (root_path / name).rmdir()

Upvotes: 0

acapola
acapola

Reputation: 1243

Unfortunately, shutil.rmtree is not enough when some directories are read-only. I ended up with this (tested on Mac-OS):

def rmtree(path):
    if not os.path.exists(path):
        return
    DIR_READ_WRITE = 0o700
    FILE_READ_WRITE = 0o600
    def remove_readonly(func, path, _):
        "Clear the readonly bit and reattempt the removal"
        os.chmod(path, FILE_READ_WRITE)
        func(path)

    # set all directories as read/write
    os.chmod(path, DIR_READ_WRITE)
    for root, dirs, files in os.walk(path):
        for d in dirs:
            os.chmod(os.path.join(root, d), DIR_READ_WRITE)

    shutil.rmtree(path, onerror=remove_readonly)

Upvotes: 0

buhtz
buhtz

Reputation: 12152

Here is a pythonic and recursion-less solution

>>> for e in sorted(p.rglob('**/*'), key=lambda v: v.is_dir()):
...     try:
...         e.unlink()
...     except IsADirectoryError:
...         e.rmdir()

The rglob() gives you all files and directories recursively in the path p. The sorted() with its key argument takes care that the result is ordered by files first and directories at the end. This makes it possible to make all directories empty with deleting their files first.

The try...except... part prevents you from using cheap if statements.

Upvotes: 1

Cireo
Cireo

Reputation: 4427

The command os.removedirs is the tool for the job, if you are only looking for a single path to delete, e.g.:

os.removedirs("a/b/c/empty1/empty2/empty3")

will remove empty1/empty2/empty3, but leave a/b/c (presuming that c has some other contents).

    removedirs(name)
        removedirs(name)
        
        Super-rmdir; remove a leaf directory and all empty intermediate
        ones.  Works like rmdir except that, if the leaf directory is
        successfully removed, directories corresponding to rightmost path
        segments will be pruned away until either the whole path is
        consumed or an error occurs.  Errors during this latter phase are
        ignored -- they generally mean that a directory was not empty.

Upvotes: 3

lqs
lqs

Reputation: 1454

The default behavior of os.walk() is to walk from root to leaf. Set topdown=False in os.walk() to walk from leaf to root.

Upvotes: 39

Garvit
Garvit

Reputation: 400

For Linux users, you can simply run the shell command in a pythonic way

import os
os.system("rm -r /home/user/folder1  /home/user/folder2  ...")

If facing any issue then instead of rm -r use rm -rf but remember f will delete the directory forcefully.

Where rm stands for remove, -r for recursively and -rf for recursively + forcefully.

Note: It doesn't matter either the directories are empty or not, they'll get deleted.

Upvotes: -2

microo8
microo8

Reputation: 3774

Try rmtree() in shutil from the Python standard library

Upvotes: 18

mitch
mitch

Reputation: 1900

Here's my pure pathlib recursive directory unlinker:

from pathlib import Path

def rmdir(directory):
    directory = Path(directory)
    for item in directory.iterdir():
        if item.is_dir():
            rmdir(item)
        else:
            item.unlink()
    directory.rmdir()

rmdir(Path("dir/"))

Upvotes: 45

Monir
Monir

Reputation: 1660

The command (given by Tomek) can't delete a file, if it is read only. therefore, one can use -

import os, sys
import stat

def del_evenReadonly(action, name, exc):
    os.chmod(name, stat.S_IWRITE)
    os.remove(name)

if  os.path.exists("test/qt_env"):
    shutil.rmtree('test/qt_env',onerror=del_evenReadonly)

Upvotes: 6

pepoluan
pepoluan

Reputation: 6780

Here's another pure-pathlib solution, but without recursion:

from pathlib import Path
from typing import Union

def del_empty_dirs(base: Union[Path, str]):
    base = Path(base)
    for p in sorted(base.glob('**/*'), reverse=True):
        if p.is_dir():
            p.chmod(0o666)
            p.rmdir()
        else:
            raise RuntimeError(f'{p.parent} is not empty!')
    base.rmdir()

Upvotes: 1

Tobias Ernst
Tobias Ernst

Reputation: 4634

Here is a recursive solution:

def clear_folder(dir):
    if os.path.exists(dir):
        for the_file in os.listdir(dir):
            file_path = os.path.join(dir, the_file)
            try:
                if os.path.isfile(file_path):
                    os.unlink(file_path)
                else:
                    clear_folder(file_path)
                    os.rmdir(file_path)
            except Exception as e:
                print(e)

Upvotes: 1

Justus Wingert
Justus Wingert

Reputation: 502

Just for the next guy searching for a micropython solution, this works purely based on os (listdir, remove, rmdir). It is neither complete (especially in errorhandling) nor fancy, it will however work in most circumstances.

def deltree(target):
    print("deltree", target)
    for d in os.listdir(target):
        try:
            deltree(target + '/' + d)
        except OSError:
            os.remove(target + '/' + d)

    os.rmdir(target)

Upvotes: 8

Gajender
Gajender

Reputation: 117

better to use absolute path and import only the rmtree function from shutil import rmtree as this is a large package the above line will only import the required function.

from shutil import rmtree
rmtree('directory-absolute-path')

Upvotes: 8

Related Questions