Newbie Question about os.removedirs()

Robert Weisser r.weisser at att.net
Thu Mar 28 17:30:46 EST 2002


Hello:

A am new to Python and to newsgroups.  In fact this is my first post.

Since my backup batch file from my Win98 computer would not work
on my new Win2000 computer (no more deltree), I wrote the following
backup Python script (my first not totally trivial Python program).

I wanted to make it as Pythonesque as possible, but I had a problem
with the os.removedirs function.  It would not delete a directory tree
if the tree contained a read only file.  Actually it may have deleted
part of the tree before failing.  I should have checked but didn't.

I assume os.removedirs is calling os.remove.  At the interactive prompt
I tried os.remove on an individual read-only file, which also failed.
The error is OSError: [Errno 13] Permission denied:  filename.

If I have to, I will write a recursive function which uses os.remove in
a try block.  If os.remove fails I will change the mode of the file.  I
assume Python supports the equivalent of unix chmod (haven't checked yet).
However, it would be nicer if there is an option to os.removedirs so
that I won't have to do this.

Because I am new to Python (although not to programming) comments
on the code would also be appreciated.  I apologize for its length.

IMPORTANT (to me, anyway) NEWBIE QUESTION:  What does Debian mean?

Here is my code with sample input files following.  The problem
statement is 16 lines from the end.

# BackupToZip.py

""" Backup selected directories or files to the zip drive, normally e:
    Files and directories used:
        COMPFILE - contains name of computer being backup up
        DIRFILE - contains list of dirs to backup
        FILEFILE - contains list of files to backup
        DRIVEFILE - contains letter of zip drive (target drive)
        INDICATOR - a file on the backup desk which confirms that
            the disk is a backup up disk for this computer.  For
            example, if the computer is named bentley, there should
            do a file named bentley_backup_disk on the backup disk.
     If it is not there, the program warns the user and
     then offers to create the indicator file for the
     next time.
        SCRIPTDIR - directory containing this program
            and the three files above
"""

# improvements needed:
#  use os.removedirs(dirName) instead of
#    os.system('rmdir /s/q ' + quoteit(dirName))
#  make the directory listing for the user portable
#  make it possible to exclude files and directories from
#    the backup

# the commented out print statements were used for debugging

import glob, os, sys, string, shutil

COMPFILENAME = 'CompName.txt'     # this file contains name of computer,
                                                         #  e.g. bentley
DIRFILENAME = 'DirList.txt'       # this file contains the list of
                                                       #  directories to be
backed up
FILEFILENAME = 'FileList.txt'     # this file contains the list of
                                  #  individual files to be backup up
                                 #  (may contain wild cards)
DRIVEFILENAME = 'ZipLetter.txt'   # this file contains the drive letter
                                                          #  of the zip
drive
SCRIPTDIR = sys.path[0]    # directory containing this script
INDICATORSUFFIX = '_backup_disk'  # used to construct indicator
                                                               #  file name,
e. g.
                                                              #
bentley_backup_disk
LINE = '-' * 70

def getListFromFile(fileName):
    """ read a list from a file.
        ignore comments and blank lines.
    """
    lines = open(fileName).readlines()
    return cleanLines(lines)

def getLetterFromFile(fileName):
    """ get one letter from a file.
        ignore comments and blank lines.
    """
    lines = open(fileName).readlines()
    lines = cleanLines(lines)
    return lines[0][:1]

def getFirstLineFromFile(fileName):
    """ get a name from a file.
        ignore comments and blank lines.
    """
    lines =  open(fileName).readlines()
    lines = cleanLines(lines)
    return lines[0]

def cleanLines(lines):
    """ remove newlines, comments, and blank lines from
        a list of lines
    """
    res = []
    for line in lines:
        line = removeComment(line).strip() # remove comments and white space
        if line:       # ignore zero length lines
            res.append(line)
    return res

def removeComment(line):
    """ remove comments from lines.
        a comment starts with a '#' and continues to the
        end of the line.
    """
    try:
        i = line.index('#')
        line = line[:i]
    except ValueError:
        pass
    return line

def caseInsensitiveCompare(arg1, arg2):
    "case insensitive compare for use with filter and sort"
    return cmp(string.lower(arg1), string.lower(arg2))

def getAns(msg, *values):
    """ Prompts using msg and the items in values.
        Reads in an answer until the user enters one of the
        items in values.  Returns the answer.
        Example:  when called as getAns("junk", "y", "n"), the
        prompt is "junk (y/n)? ".
        The items should be single characters.  Only the first
        letter of the response is used.
    """
    valString = string.join(values, '/')
    ans = None
    while ans not in values:
        ans = string.lower(raw_input(msg + ' (' + valString + ')? ')[0])
    return ans

def backupDir(dirName, driveRoot):
    """ copies the dirName directory to the root directory
        of the target drive.
    """
    print LINE
    if not os.path.isdir(dirName):
        print 'can not backup: ', dirName, '(not found or is not a
directory)'
        return
    targetName = driveRoot[0:2] + dirName[2:]
    targetDir = os.path.dirname(targetName)
    createDir(targetDir)
    print 'backing up: ', dirName
    print '        to: ', targetName
    shutil.copytree(dirName, targetName)
    # the non-Python method which I used at first:
    # xcopyCmd = 'xcopy /s/e ' + quoteit(dirName) + ' ' +
quoteit(targetName)
    # print xcopyCmd
    # rc = os.system(xcopyCmd)
    # print 'Return code from xcopy: ', rc

def backupFile(fileName, driveRoot):
    """ first run glob on the fileName to get all files whose names match
        the fileName pattern, then copy them to the backup drive.
    """
    print LINE
    fileList = glob.glob(fileName)
    if not fileList:
        print 'can not backup: ', fileName, '(not found)'
        return
    targetPattern = driveRoot[0:2] + fileName[2:]
    targetDir = os.path.dirname(targetPattern)
    createDir(targetDir)
    print 'backing up: ', fileName
    print '        to: ', targetPattern
    for filen in fileList:
        if os.path.isfile(filen):
     targetFile = driveRoot[0:2] + filen[2:]
     print filen
     print '  -->', targetFile
     shutil.copy2(filen, targetFile)
 else:
     print 'can not backup: ', filen, '(not a regular file)'

def createDir(dirName):
    "create a directory if it does not exist"
    if dirName != driveRoot and not os.path.exists(dirName):
        print 'creating', dirName
        print
        os.makedirs(dirName)

def quoteit(str):
    "put quotes around a string"
    return '"' + str + '"'

# this function is not used in this program,
# although I originally planned to use it.
def rmtreeError(func, path, excinfo):
    "error handler for shutil.rmtree"
    print `func`, 'error: ', path
    print excinfo

# ----- start of processing -----

# important variables:

# delDirList:  directories on target drive which will be deleted
# delFileList:   files on target drive which will be deleted
# dirList:  directories to back up
# fileList:  files to back up

# read the configuration files

print
print "Starting backup"

os.chdir(SCRIPTDIR)
# print 'starting dir:', os.getcwd()

backupDrive = getLetterFromFile(DRIVEFILENAME) # drive letter
print 'Backup will take place on Drive', backupDrive
driveRoot = backupDrive + ':\\'

compName = getFirstLineFromFile(COMPFILENAME) # name of this computer
print 'Computer name: ', compName

dirList = getListFromFile(DIRFILENAME)  # directories to back up
dirList.sort(caseInsensitiveCompare)
# print 'Directories to back up: ', dirList

fileList = getListFromFile(FILEFILENAME) # files (patterns)
      #  to back up
fileList.sort(caseInsensitiveCompare)
# print 'Files to backup up: ', fileList

# change directory to the backup drive

rc = os.chdir(driveRoot)
# I used the Windows dir command here because I wanted
# the output to be the familiar Windows dir command output.
# I may make this more portable if I start using it elsewhere.
print
rc = os.system('dir /o')

delFileList = os.listdir('.')
delFileList.sort(caseInsensitiveCompare)
delDirList = filter(os.path.isdir, delFileList)   # dirs on target drive
# print 'delDirList:', delDirList    #  which will be deleted
delFileList = filter(os.path.isfile, delFileList) # files on target drive
# print 'delFileList:', delFileList    #  which will be deleted

indicatorFileName = compName + INDICATORSUFFIX
if indicatorFileName in delFileList:
    delFileList.remove(indicatorFileName)
else:
    print
    print 'Indicator file', indicatorFileName, 'not found in', driveRoot
    print 'Are you sure this is a', compName, 'backup disk?'
    ans = getAns('Do you want to continue?', 'y', 'n')
    if ans == 'y':
        print
        print 'creating file', indicatorFileName
        indFile = open(indicatorFileName, 'w') # create the indicator file
        indFile.close()    #  as an empty file
    else:
        print 'Backup cancelled'
 raw_input('Press <enter> to continue:')
        sys.exit('Program terminated by user request')

print
print 'WARNING:  all files and directories on drive ' + backupDrive + ':
except'
print '          the file', indicatorFileName, ' will be deleted.'
ans = getAns('          Continue', 'y', 'n')
if ans == 'n':
    print 'Backup cancelled'
    raw_input('Press <enter> to continue:')
    sys.exit('Program terminated by user request')

# remove the directories on the backup drive

for fileName in delFileList:
    print 'Removing', fileName
    os.remove(fileName)

# remove the files on the backup drive

for dirName in delDirList:
    print 'Removing directory', dirName
    rmCmd = 'rmdir /s/q ' + quoteit(dirName)
    # I had to use non-portable os.system call (see note on os.removedirs
line)
    # print 'rmCmd:', rmCmd
    os.system(rmCmd)
    # os.removedirs(dirName) # would not remove a directory
       # tree with a readonly file in it

# finally, do the backup.

# first copy the complete directories:
for dirName in dirList:
    backupDir(dirName, driveRoot)

# now do the files:
for fileName in fileList:
    backupFile(fileName, driveRoot)

print LINE
print 'Backup has been completed'
raw_input('Press <enter> to continue:')

---------------- end of code -------------------

Contents of CompName.txt:

    # The name of the computer being backed up.
    # The program checks for a file called compname_backup_disk
    # on the backup disk and prints a warning if it is not found.

    bentley

------------------------------------------------

Contents of DirList.txt:

    # List of Directories which will be copied to the zip drive
    # The drive letters must be included in the directory names

    C:\Documents and Settings\Robert Paul Weisser\My Documents
    C:\My Installations
    C:\RPW
    C:\Junk\Stuff
    C:\Documents and Settings\Robert Paul Weisser\Application Data

------------------------------------------------

Contents of FileList.txt:

    # Files to backed up.
    # File name metacharacters are allowed.
    # The drive letters must be included in the file names

    C:\Appl1\*.cpp
    C:\Appl1\*.xls

    C:\Appl2\*.cpp
    C:\Appl2\*.xls

------------------------------------------------

Contents of ZipLetter.txt:

    # The drive letter of the backup drive (normally a zip drive).
    # Everything after the first character is ignored.

    E

------------------------------------------------

Thanks,
Robert Weisser (r.xweisser at att.net)
(take out the x - I see other people doing this so I guess it's a
good idea)





More information about the Python-list mailing list