Search code examples
pythonrecursiondirectorysymlinkdelete-file

Delete directory and all symlinks recursively


I tried to use shutil to delete a directory and all contained files, as follows:

import shutil
from os.path import exists
if exists(path_dir):
    shutil.rmtree(path_dir)

Unfortunately, my solution does not work, throwing the following error:

FileNotFoundError: [Errno 2] No such file or directory: '._image1.jpg'

A quick search showed that I'm not alone in having this problem. In my understanding, the rmtree function is equivalent to the rm -Rf $DIR shell command - but this doesn't seem to be the case.

p.s. for reconstruction purposes. Please create a symbolic link for example using ln -s /path/to/original /path/to/link


Solution

  • That is strange, I have no issues with shutil.rmtree() with or without symlink under the folder to be deleted, both in windows 10 and Ubuntu 20.04.2 LTS.

    Anyhow try the following code. I tried it in windows 10 and Ubuntu.

    from pathlib import Path
    import shutil
    
    
    def delete_dir_recursion(p):
        """
        Delete folder, sub-folders and files.
        """
        for f in p.glob('**/*'):
            if f.is_symlink():
                f.unlink(missing_ok=True)  # missing_ok is added in python 3.8
                print(f'symlink {f.name} from path {f} was deleted')
            elif f.is_file():
                f.unlink()
                print(f'file: {f.name} from path {f} was deleted')
            elif f.is_dir():
                try:
                    f.rmdir()  # delete empty sub-folder
                    print(f'folder: {f.name} from path {f} was deleted')
                except OSError:  # sub-folder is not empty
                    delete_dir_recursion(f)  # recurse the current sub-folder
                except Exception as exception:  # capture other exception
                    print(f'exception name: {exception.__class__.__name__}')
                    print(f'exception msg: {exception}')
    
        try:
            p.rmdir()  # time to delete an empty folder
            print(f'folder: {p.name} from path {p} was deleted')
        except NotADirectoryError:
            p.unlink()  # delete folder even if it is a symlink, linux
            print(f'symlink folder: {p.name} from path {p} was deleted')
        except Exception as exception:
            print(f'exception name: {exception.__class__.__name__}')
            print(f'exception msg: {exception}')
    
    
    def delete_dir(folder):
        p = Path(folder)
    
        if not p.exists():
            print(f'The path {p} does not exists!')
            return
    
        # Attempt to delete the whole folder at once.
        try:
            shutil.rmtree(p)
        except Exception as exception:
            print(f'exception name: {exception.__class__.__name__}')
            print(f'exception msg: {exception}')
            # continue parsing the folder
        else:  # else if no issues on rmtree()
            if not p.exists():  # verify
                print(f'folder {p} was successfully deleted by shutil.rmtree!')
                return
    
        print(f'Parse the folder {folder} ...')
        delete_dir_recursion(p)
    
        if not p.exists():  # verify
            print(f'folder {p} was successfully deleted!')
    
    # start
    folder_to_delete = '/home/zz/tmp/sample/b'  # delete folder b
    delete_dir(folder_to_delete)
    

    Sample output:

    We are going to delete the folder b.

    .
    ├── 1.txt
    ├── a
    ├── b
    │   ├── 1
    │   ├── 1.txt -> ../1.txt
    │   ├── 2
    │   │   └── 21
    │   │       └── 21.txt
    │   ├── 3
    │   │   └── 31
    │   ├── 4
    │   │   └── c -> ../../c
    │   ├── a -> ../a
    │   └── b.txt
    ├── c
    
    
    Parse the folder /home/zz/tmp/sample/b ...
    symlink a from path /home/zz/tmp/sample/b/a was deleted
    symlink c from path /home/zz/tmp/sample/b/4/c was deleted
    folder: 4 from path /home/zz/tmp/sample/b/4 was deleted
    symlink 1.txt from path /home/zz/tmp/sample/b/1.txt was deleted
    file: b.txt from path /home/zz/tmp/sample/b/b.txt was deleted
    file: 21.txt from path /home/zz/tmp/sample/b/2/21/21.txt was deleted
    folder: 21 from path /home/zz/tmp/sample/b/2/21 was deleted
    folder: 2 from path /home/zz/tmp/sample/b/2 was deleted
    folder: 1 from path /home/zz/tmp/sample/b/1 was deleted
    folder: 31 from path /home/zz/tmp/sample/b/3/31 was deleted
    folder: 3 from path /home/zz/tmp/sample/b/3 was deleted
    folder: b from path /home/zz/tmp/sample/b was deleted
    folder /home/zz/tmp/sample/b was successfully deleted!