scripts /

#!/usr/bin/env python
# coding: utf-8

A simple maintenance script for removing temporary files from given directories.

2007-2013 (c) Andy Mikhailenko

    2007-07-29	basic five-liner script with recursive cleanup
    2008-02-02	+argument processing (incl. '-r' which is now off by default)
    2013-04-13  getopt → argh; refactoring; Python3

import argh
import os
import sys

TRASHABLE_NAMES   = ('thumbs.db', 'Thumbs.db')
TRASHABLE_ENDINGS = ('.pyc', '.pyo', '~',)


def is_file_trashable(filename, trashable_names, trashable_endings):
    """ Here are the rules. Returns True if file with given filename should be deleted """
    if filename in trashable_names  or  filename.endswith(trashable_endings):
        return True

def format_file(filepath, sigil=SIGIL_FOUND):
    return '{0} {1}'.format(sigil, filepath)

def main(
    path:"set root directory to DIRECTORY (by default it's current working directory)"=None,
    name:"override default list of trashable file names with given name(s)"=None,
    ending:"override default list of trashable file endings (e.g. extensions) with given name(s)"=None,
    recursive:"clean the dirs recursively, starting with current root dir"=False,
    silent:"suppress all messages"=False,
    verbose:"report each removed file. Ignored if '--silent' is in use"=False,
    dry_run:"test mode: do not delete anything, just pretend"=False):

    path = path or os.getcwd()

    trashable_names = tuple(name.split(',')) if name else TRASHABLE_NAMES
    trashable_endings = tuple(ending.split(',')) if ending else TRASHABLE_ENDINGS

    # Report what exactly we are going to do

    if verbose and not silent:
        yield 'Cleaning up "{0}" {1}...'.format(path, (recursive and 'recursively' or '') )
        if trashable_names != TRASHABLE_NAMES  or  trashable_endings != TRASHABLE_ENDINGS:
            yield '*** customized behaviour ***'
        action = 'Searching for' if dry_run else 'Removing'
        yield ('{action} files with names matching {names} or ending with '
               '{endings}...'.format(action=action, names=trashable_names,

    # Walk and wipe

    matched_cnt = removed_cnt = 0
    for root, dirs, files in os.walk(path):
        for fname in files:
            if not is_file_trashable(fname, trashable_names, trashable_endings):
            matched_cnt += 1
            filepath = os.path.join(root, fname)
            if dry_run:
                if verbose and not silent:
                    yield format_file(filepath)
                if verbose and not silent:
                    yield format_file(filepath, SIGIL_REMOVED)
                removed_cnt += 1
            except OSError as e:
                yield format_file(str(e), SIGIL_ERROR)
        if not recursive:

    # Report results

    if not silent:
        if not matched_cnt:
            return 'Already clean'
        if dry_run:
            return 'Found {0} item(s). Nothing removed.'.format(matched_cnt)
        if matched_cnt == removed_cnt:
            return 'Removed {0} item(s). Now clean.'.format(matched_cnt)
        return ('Tried to remove {0} item(s): {1} OK, {2} could not be removed '
                '(see log for details)'.format(matched_cnt, removed_cnt,

if __name__ == '__main__':