. . . .

Python Examples

 

updated:2018.05.14

Prev   Next   Site Map   Home  
Text Size
Please reload page for style change

PYTHON

Using Python requires no further justification than that it is available on practically all computers and affords an extraordinary range of general and specialized libraries. But the language itself is well designed. It is at once both theoretically interesting and practical. It borrows judiciously from other languages and introduces entirely new ideas only where justified. Instead of encouraging meaningless idiosyncratic variations, it provides different idioms clearly intended to support different circumstances.

One of Python’s theoretical strengths is in its use of anonymity to reduce scaffolding code, which exists not to express intent but because the language needs it to know what to do. For example, all new languages invented after ALGOL recognize functions as expressions whose value is whatever the function returns. There is no need to assign the return to a variable just to use it. At its inception LISP carried this “first-classness” to the extreme and was the first language to support lambda expressions.

Python is the first language to extend anonymity beyond LISP by giving first-classness to object initialization lists. This alone establishes it as seminal in the development of synthetic languages. But it is also very practical, reducing programming coordination cost in a variety of circumstances. For example, to process a command line in which -C, -B, -F, -R, and -S are valid command options, we might use cmd = 'CBFRS'.find(sys.argv[1][1:2].upper()). In any other language, the String initializer 'CBFRS' would have to be assigned to a superfluous variable before it could be used and that assignment would typically be located far away from its one-time use.

Another of Python’s original ideas clearly motivated by practical programming concerns as well as an appreciation of theoretical principles is a variety of generalized block termination branches. For example, Python generalizes else as a block predicate for any block that completes, including a loop that reaches its parameterized conclusion without being conditionally terminated. In practical terms this means that, for example, a loop searching for something can branch one way if it finds what it is looking for and another if it doesn’t, avoiding an ad hoc duplicate test while imparting greater structure to the code.



IMPROVED CD


# File: dSup.py - list version (vs. deque)
# ............................................................................
import sys, os, msvcrt

ESC = b'\x1b'

def indexNoCase(slist, s):
    for idx in range(len(slist)):
        if slist[idx].upper() == s.upper():
            return idx
    raise ValueError

# .................. main process ............................................
if len(sys.argv) < 2:
    cmd = 1 # No argument defaults to -B, the most common operation
elif sys.argv[1][0] == '-':
    if len(sys.argv[1]) == 1:
        cmd = 2 # '-' alone defaults to -F, second most common operation.
    else:
        cmd = 'CBFRS'.find(sys.argv[1][1:2].upper())
else:
    cmd = -1 # User typed a target directory
# cmd is -1 = path, 0 = C, 1 = B, 2 = F, 3 = R, 4 = S

# fo = open(os.path.dirname(sys.argv[0]) + '\\dSupList', mode = 'a+t')
fo = open( os.getenv('HOME', 'TEMP') + '\\dSupList', mode = 'a+t')

fo.seek(0)
dlist = fo.readlines(-1)
olist = dlist[:] # Copy for checking list change.
# If the directory list is new or for some other reason doesn't contain CWD 
# then add it. If the list exists and contains CWD but not at the head then 
# move it to the head.
try:
    target = os.getcwd() + '\n'
    idx = indexNoCase(dlist, target)
    if idx != 0:
        del dlist[idx]
        dlist.insert(0, target)
except ValueError:
    dlist.insert(0, target)

if cmd == 1: # B: move backward, i.e. to previous, by rotate left.
    dlist.append(dlist.pop(0))
elif cmd == 2: # F: move forward, i.e. to next, by rotate right.
    dlist.insert(0, dlist.pop())

elif cmd == 3: # R: remove a dir from the list.
    if len(sys.argv) < 3: # -R w/o arg means to remove current from list.
        del dlist[0]
    else:
        try:
            dir = " ".join(sys.argv[2:])
            del dlist[indexNoCase(dlist, dir + '\n')]
        except ValueError:
            print('"', dir, '" is not in the list', sep='')
            exit(2)
        
elif cmd == 4: # S: select from list
# The current directory (dlist[0]) is included in the list. From the user's 
# perspective this is essentially "do nothing" so we don't need to provide an 
# Esc option.
    for i, d in enumerate(dlist):
        print('(' + str(i) + ')', d, end = "")
    while True:
        inp = msvcrt.getche() # Windows-specific Enter-less key input.
        if inp == b'\r' or inp == ESC: inp = b'0' # Enter and ESC default to 0
        if inp.isdigit():
            inp = int(inp)
            if inp < len(dlist):
                print('') # Print the newline we didn't get from getche.
                break
        print(' is out of range')
# Select 0 means the current directory and the list is not changed. Otherwise
# the selected directory is moved to the head of the list. 
    if inp > 0:
        dlist.insert(0, dlist.pop(inp))

elif cmd == -1: # Specified path e.g. d \CmdTools\Bak
    dir = os.path.abspath(" ".join(sys.argv[1:])) + '\n'
    # Note " ".join defines space (not empty) as delimiter
    try:
        del dlist[indexNoCase(dlist, dir)]
    except ValueError:
        pass
    dlist.insert(0, dir)
 
# The list is limited to 10 items to enable selection by index 0-9. If CWD is 
# not in the list at opening, it is added. If the user requests a specific 
# directory not in the list, it is added. Consequently, the list at this point 
# may contain as many as two extra items. Chopping them off the right 
# eliminates the oldest directories in most cases. However, rotation due to 
# revisits can change time order. 
while len(dlist) > 10:
    dlist.pop()
 
# If cmd is 0 (command -C to clear the list) or the list has been changed then
# the file is truncated to nothing. For clearing it is left this way but for 
# changed list, the new dlist is written into it before closing.
if cmd == 0 or olist != dlist:
    # print('The file is being changed') 
    fo.truncate(0)
    if cmd != 0:
        fo.writelines(dlist)
fo.close()
exit(0)

See companion BAT script d.bat and equivalent Bash script cdx

When I’m working at the command line I often find that I have to visit and revisit several directories, often with long path names, which I don’t like having to type in the first place, let alone repeatedly. Windows change directory command cd is easy to remember but doesn’t provide any traversal history. The pushd directory and popd commands provide some history but it is hard to remember to use them and they only provide simple backward history, that is to retrace our traversal path. We can’t reverse course and go forward though our history and there is no means of randomly selecting a recent directory except by typing its full path.

In Linux cd is as unhelpful as it is in Windows. pushd directory and popd are somewhat more powerful than their Windows counterparts but are so unfriendly that few people bother to use them. My bash script cdx replaces the standard cd with my version, whose central feature is a circular path list, supporting forward as well as backward traversal and selection from a menu of recently visited directories. Because the Bash language interpreter is native to the Bash shell, my script can change the directory of the process that invokes it. Python is not native to Linux or Windows and cannot do this.

When a Python script is invoked, a new process is created for the Python interpreter. It inherits the parent environment but cannot change it. The native command language in Windows has long been BAT and only that could change the environment. But BAT is too crude to implement the capability required of my improved cd. I resolved this by creating a simple BAT script, called d.bat, which invokes a Python script, dSup.py, which does most of the work. BAT is so primitive that it can’t accept a return value from the Python script, so dSup.py writes the directory traversal list to a file, which the BAT script can read. The file coincidentally confers on the list persistence across sessions. Even after reboot/shutdown the list remains and opening a command window can automatically change to the last directory that had previously been open.

USE

The command is d [directory | -C | -B | -F | -R | -S]. d directory changes the directory just like cd directory but it also silently pushes the departing directory onto the head of the traversal list. -B or no argument changes to the previous directory, i.e. goes backward. -F or simply - goes forward in the traversal list. d and d - enable rapid movement backward and forward through a sequence of directories. -S displays the traversal list as a menu, enabling selecting any recently visited directory by number (single key press). -C clears the traversal list. -R directory removes the given directory from the list. -R alone removes the current directory from the list and returns to the previous.

GENERAL OPERATION

d.bat exists only because a Python script cannot change the user’s directory. d.bat does as little as possible. It doesn’t interpret the command line but simply passes it through to dSup.py. dSup.py reads the traversal list file, pushes the target directory to the head, and writes the list back to the file. The target directory is the first line of the file. When Python returns, d.bat reads the first line of the same file and tries to cd to it. If this fails, it reinvokes dSup.py with the argument -R causing the bad directory to be removed from the list. If the user tries to change to a directory that either doesn’t exist or that they are not allowed to access, the response appears to be the same as for the cd command.

It might seem that the means of handling a bad directory is unnecessarily complicated when the Python script could simply reject bad directories but there are two problems with this. One is that Windows doesn’t always provide complete access rights information, especially for remote (network) directories. And dSup.py has no way to communicate with d.bat other than through the traversal list file, complicating error handling. These are not insurmountable problems but their solution is more complicated than the try-remove approach I’ve taken.

DIRECTORY LIST

The list is stored in the dSupList file, which is located in %HOME%. Each directory is stored on one line. The first is the target destination used by d.bat after invoking dSup.py. It will become the current (if not already) if the cd command succeeds. Otherwise, d.bat reinvokes this script with the -R argument to request that the bad directory be removed. -R also provides a means for the user to remove the current directory and revert to the previous (-R) or to remove any directory in the list (-R directory ). The requested directory is moved or inserted at the head (left end) of the list, making the list a most-recent-first history.

The file is opened for both reading and writing. It is initially read into dlist to get the history. If the list becomes changed in command processing (including unpredictable interaction in the -S select option) it will be written back into the file, entirely replacing the contents. Otherwise, we just want to close the file. Rather than tracing the status of dlist, a copy is made before any other processing, after which, if dlist and the copy are still equal, the file is not written.

This is especially valuable for a specific but quite frequent scenario. The command prompt shortcut used to open a command window from the GUI opens onto whatever directory is specified in the link. This is generic and rarely where we have been recently working. If we just If we just invoke d at that point, the directory will be changed to the most recent one and the list does not need to change, saving time and reducing disk wear. This is so handy that it is worthy of being a permanent part of any general command prompt link, i.e. %SystemRoot%\system32\cmd.exe /K d.bat.

TRAVERSAL HISTORY

If each visited directory were unique, moving backward and forward through the list would be a simple rotation left (backward) or right (forward). But the user may type or select a directory that is already in the list. Just inserting it at the head, creating a duplicate, could quickly fill the list with one or two dominant directories (imagine toggling between two several times). To avoid this the directory is either picked up and moved to the head or the list is rotated, positioning the directory at the head.

Rotation sounds good but in actual use is inferior because it promotes ancient history over more recent. e.g. traversal K-L-M-N-A-B-C creates list C-B-A-N-M-L-K. Then select M would change the list by rotation to M-L-K-C-B-A-N. Then backward move would go to L rather than C as the user would expect. If pick/insert were used instead the list would become M-C-B-A-N-L-K and moving backward would retrace more recent history C-B-A. The N-L context of M would be lost but this might reflect the user’s intent in picking it out of context.

DIRECTORY COMPARISON

To avoid duplications in the list, before an explicit (user-entered) directory is inserted, any matching element of the list is removed. To avoid being confused by aliases, e.g. ..\Bak from C:\Work\Src = C:\Work\Bak and irrelevant case differences, the requested directory is always normalized to full path for storing in the list and compare is case-insensitive. Therefore, we have to use our own indexNoCase function to search the list instead of list.index().

LIST SELECT

The option to select a directory from the traversal list supports single-key input (0-9) using getche, a function available through msvcrt. Although the equivalent functionality is complicated in Linux, it is simple in Windows. For a simple universal interface this could be replaced by input() which requires two key presses, the number followed by Enter.

REMOVE DIRECTORY

The -R option tells to remove a directory from the list. If a directory name is given, the list is searched for that. Otherwise, the head (left end) of the list is removed. This is always the current directory. If d.bat tries and fails to go to the directory at the head then it recalls this script, passing -R with the name of the directory that failed. The bad directory is by definition at the head of the file list, but it must still be provided in the command line because this (dSup.py) script always moves or adds the current directory to the head before processing commands.

-R without directory is useful when the user goes to a directory and finds it useless. -R clears the record while returning to the previous. The user will most often use -R with a name to remove from the list directories that have been deleted from the system or that interfere with -B and -F. Alternatively, with the -S option, the bad directory can be selected; it will then fail and be automatically removed. If the list gets really bad, -C clears it completely (except for CWD).

SPECIFIED PATH

If the command line specifies a path (rather than one of the - options) and the path is quoted or contains no spaces then it is the one argument sys.argv[1]. For user convenience, quoting is not necessary even if the path contains spaces, in which case there will be multiple arguments. We can sweep up all of these using sys.argv[1:] but this creates a list rather than a string. The string method " ".join recombines the list’s elements back into a single space-delimited string. Path history requires absolute paths in order to function logically so os.path.abspath is applied to this in case it is relative. Finally, a newline is appended to prepare the path string for insertion into the path list.

The -R option takes an optional path argument. Join is also used in this case to accept as one argument a path containing spaces. Here, everything after the first argument is swept up into one string using " ".join(sys.argv[2:]).

LIST VS DEQUE

The list may be implemented as a list or deque. deque.appendleft is more efficient than list.insert(0) but list more efficiently moves a directory at a random position to the head. I have written two versions of this, one using list and the other deque. The deque version includes a general move to head function where the list version has one insert(pop). The two versions behave identically and there is no noticeable speed difference.

The -B command essentially traverses from the head (most recent) to the tail (oldest) by rotating the list to the left. The deque version does this simply by dlist.rotate(-1). The list version is not much more complicated, using insert(pop). -F moves forward through the list by rotating right.



CLONE UPDATE SCRIPT


# File: cpxUpdate.py
# ............................................................................
import sys, os
from msvcrt import getche
from os import system

# ----------------------------------------------------------------------------
# Class flushfile replaces the builtin stdout write with one that calls flush 
# after write. Newer Python versions of file.write take an optional flush 
# argument but making flush the default is easier and ensures that it is not 
# forgotten. This should always be used for interactive command line scripts.
# ............................................................................
class flushfile:
    def __init__(self, f):
        self.f = f

    def write(self, x):
        self.f.write(x)
        self.f.flush()

    def flush(nothing): return # Prevents warning at end

sys.stdout = flushfile(sys.stdout)

# ----------------------------------------------------------------------------

Thumb = 'J:'
Ref = 'X:'
ESC = b'\x1b'
doAll = False

# ----------------------------------------------------------------------------
# Function: ask
# Purpose:  General single-keystroke user query. Checks keypress against given
# acceptible responses and repeatedly queries the user until the key is legal.
# Returns:  index of matching key code.
# Arguments: opt is a tuple whose first element is a query String and second a 
# Bytes list of acceptible key codes. This is a tuple instead of individual 
# arguments to facilitate shared patterns (every usage requires a matched pair)
# ..............................................................................
def ask(opt) :
    while True :
        print(opt[0], end = '? ')
        idx = opt[1].find(getche().upper())
        if idx >= 0:
            print('') # Print the newline we didn't get from getche.
            return idx
        print(' is not an option. Try again.') 

# ------------------------------------------------------------------------------
# Function: askYn
# Purpose:  Simple shell over ask. This is a frequent use of ask. The user is 
# presented three options, Esc to abort, Y, and N. It could be used for other 
# things but primarily it is used to ask the user whether to update a 
# directory. Y means to do it. N means to skip it. Esc means to stop executing 
# the script. Returns: 1 if Y, 2 if N. On Esc this doesn't return but 
# immediately exits.
# ............................................................................
def askYn() :
    idx = ask(('[Esc,Y,N]', ESC + b'YN'))
    if idx == 0 :
        exit(0)
    return idx
    
# ----------------------------------------------------------------------------
# Function: askAbort
# Purpose:  Queries the user whether to abort or continue. Pressing Esc selects
# abort. Any other key selects continue. This function does not exit on Esc but
# returns the response. 
# Returns: True if the user presses Esc, False for any other key.
# ..............................................................................
def askAbort() :
    print('Press Esc to stop or any other key to continue: ', end = '') 
    inp = getche().upper()
    print('') # newline we didn't get from getche
    if inp == ESC :
        return True
    else :
        return False
    
# ------------------------------------------------------------------------------
def queryUpdate(clone) :
    print('Do you want to update ' + clone, end = '')
    return askYn()

# ------------------------------------------------------------------------------
# Function: update
# Purpose: Directory update process. First asks whether to update the given 
# clone directory. The user can enter Esc to abort the script, Y to update, or 
# N to skip this directory. If Y then compx is invoked twice, first with a 
# command to replace older and missing clone files with files from ref, and 
# then with a command to prune dead clone files.
#
# Returns: Doesn't return if user selects Esc; False if N. If user selects Y, 
# after updating the user is given a chance to Abort, in which case the script 
# exits reflecting the return code from compx, or continue, in which case True 
# is returned (to the caller).
#
# Arguments:
# - clone and ref are the clone and reference full path names. clone may refer 
# to a thumb drive or computer, in either case as identified by drive letter. 
# ref could also be flexible but, in practice, is always a hard drive.
# - args is a tuple of two Strings, the first and second compx invocation 
# option arguments. The full command line also includes the clone and reference
# directories but the clone and ref arguments provide this information. args 
# is a two-tuple instead of two String arguments to make it easier for callers 
# for different directories to share common argument sets. e.g. argsA in the 
# Doc group. Be sure that there is a comma between the two strings. Two 
# strings without this would  be concatenated, making args[0] a too-long 
# command line and args[1] empty without encountering a syntax error.
# ............................. notes ........................................
#                               doAll
# If doAll then every directory in the list is processed without asking and, 
# unless the compx program returns an error, there is no pause after 
# processing.
# ............................................................................
def update(clone, ref, args) :
    if not doAll :
        if queryUpdate(clone) == 2 : # User says no to update
            return False
    prune = os.path.exists(clone)
    for cmd in ('compx ' + ref + clone + args[0], 'compx ' + clone + ref + args[1]) :
        print(cmd)
        ret = system(cmd)
        if (not doAll or ret > 0) and askAbort() :
            exit(ret)
        if not prune :
            break
    return True
    
# ---------------------------------- BEGIN -------------------------------------    
print('cpxUpdate 2016.05.07')
print('Esc: Abort')
print('1: Update DocDavid thumb drive (' + Thumb + ')')
print('2: Update Doc thumb drive (' + Thumb + ')')
print('3: Update this computer from reference computer (' + Ref + ')')
idx = ask(('[Esc,1,2,3]', ESC +  b'123'))
if idx == 0 :
    exit(0)
print('Do you want to process all directories without asking ', end = '')
if askYn() == 1 :
    doAll = True

if idx == 1 : #---- Update DocDavid thumb drive ------------------------------
    update(Thumb + '\\DocDavid ', 'C:\\DocDavid ',
            ('-Q4 -OA1 -S-Bak -U -Y', '-OA1 -S-Bak -U1'))
  
if idx == 2 : # ------- Update Doc Thumb Drive -------------------------------
    argsA = ('-Q4 -OA1 -S-Bak -U -Y', '-OA1 -S-Bak -U1')
    update(Thumb + '\\CmdTools ', 'C:\\CmdTools ', ('-Q4 -OA1 -U', '-OA1 -U1'))
    update(Thumb + '\\Doc ', 'C:\\Doc ', argsA)
    update(Thumb + '\\SwDev ', 'C:\\SwDev ',
            ('-Q4 -OA1 -S3-Bak,Old,Debug,Release -U -Y',
            '-OA1 -S3-Bak,Old,Debug,Release -U1'))
    update(Thumb + '\\Media\\Pub ', 'C:\\Media\\Pub ',
            ('-Q4 -OA1 -U -Y', '-OA1 -U1'))
    update(Thumb + '\\Web\\Portfolio ', 'C:\\Web\\Portfolio ', argsA)
    update(Thumb + '\\Arc ', 'C:\\Arc ', argsA)
    update(Thumb + '\\DocPub ', 'C:\\DocPub ', argsA)

if idx == 3 : # --------- Update this computer from reference ----------------
    argsA = ('-Q3 -OA1 -S-Bak -U -Y', '-OA1 -S-Bak -U1')
    update('C:\\CmdTools ', Ref + '\\CmdTools ',
            ('-Q3 -OA1 -U', '-OA1 -U1'))
    update('C:\\DocDavid ', Ref + '\\DocDavid ', argsA)
    update('C:\\Doc ', Ref + '\\Doc ', argsA)
    update('C:\\SwDev ', Ref + '\\SwDev ',
            ('-Q3 -OA1 -S3-Bak,Old,Debug,Release -F-launch,ncb,opt,plg -U -Y',
              '-OA1 -S3-Bak,Old,Debug,Release,Lpcx*,Mcux* -U1'))
# This compares any Lpcx or Mcux directories that exist in source but doesn't
# ask whether to remove them on this computer.
    update('C:\\Media\\Pub ', Ref + '\\Media\\Pub ',
            ('-Q3 -U -Y', '-OA1 -U1')) # Note no -OA1 in first command
    update('C:\\Web\\Portfolio ', Ref + '\\Web\\Portfolio ', argsA)
    update('C:\\Arc ', Ref + '\\Arc ', argsA)
    update('C:\\DocPub ', Ref + '\\DocPub ', argsA)

    # The rest of these are only by request (of top level) regardless of doAll
    # but we don't need to ask if doAll is False because, in that case, update
    # will ask on the top level as well as all sub-directories.
    if doAll == False or queryUpdate('C:\\DocPubCommon ') == 1 :
        update('C:\\DocPubCommon ', Ref + '\\DocPubCommon ', argsA)

I do most of my work on one computer with regular backups to archival devices and a mirror computer. The mirror computer is a convenient second debugger when I’m developing software for multiple embedded targets but its main purpose is to allow immediate switch-over if something goes wrong with my main computer or a long job is consuming most of its bandwidth.

On my main computer I do a wide variety of work with different backup requirements. I want to be able to either include or exclude directories and files by name, including wild-cards; to control recursive depth; to optionally prune dead stuff from the clone (mirror computer or archive); to control the extent of automation (i.e. things that are done without asking for approval) on a case-by-case basis; and to execute very rapidly so that I don’t mind regularly invoking it.

Standard backup management programs don’t provide all of the capabilities that I need and they are much slower than I want. I decided on a Unix-like solution, breaking the problem into two parts, a fast and flexible file compare program with a more user-friendly Python front end.

DESIGN

The file compare program and its myriad command-line options do most of the work. Python could handle this very complex job but it would be much slower than the bare-metal C program. Consequently, this is a simple script, which could be done by less capable languages than Python. However, Python’s sophistication is not wasted.

This script is what I use for back up to two different USB thumb drives and a mirror (“this”) computer. compx is invoked twice for many different directory trees, first to add/update clone directories and files and then to prune the clone of directories and files that have been removed from the reference computer. Instead of repeating the complex invocations, burying important variations in a mountain of repetition, I have written the function update to handle the task generically. The invocations of update are essentially declarative. I could have condensed the code even further by iterating over a table of the argument sets but the repeating statements invoking update are as regular and nearly as condensed as the table would be.

PURPOSE

Purpose: Update clones from reference computer. Clones may be thumb drives, in which case the computer is the reference and C: is the reference drive. Alternatively, “this” computer is the clone and a computer on the network whose C: is mapped to another drive letter is the reference. This script works only in Windows. It contains a Windows-specific simplified means of providing single keystroke input but, more fundamentally, drive letters are essential to identifying reference and clone.

DRIVES

The script assigns X and J drives to Ref and Thumb. When updating the Doc or DocDavid thumb drive, ref is automatically C: and the clone (in both cases) is J. When updating “this” computer, C: is clone and X is ref. The Ref and Thumb assignments made by single statements at the beginning of the script and can easily be changed. They are not options because it would be dangerous to have the user identify these by command or query, as a simple mistake could cause source files to be deleted. It would be safe to call this script from another, which could be trusted to correctly identify the drives; but there is little difference between editing the assignments in that script vs. this one.

UPDATE THUMBS VS THIS COMPUTER

Nearly identical directories and commands are used to update “this” computer as for the two thumb drives. However, drive letters are different and the compx command arguments differ in -Q4 for thumb drives vs. -Q3 for “this” computer. This controls whose time stamps get updated when file contents match but their time stamps don’t. Because of these differences, it is easier to use fully independent arguments to update for “this” computer vs. thumb drives.

The update function is primarily designed to support updating the Doc thumb drive, which has multiple directories. Since the user is given the option to selectively skip each one, this question is built into update. This question isn’t needed when updating the DocDavid thumb drive, which has only this one directory. Otherwise update is the same for either case so it is also used for DocDavid. Because the clone drive/ directory is used both to ask the user and in the compx commands, another function argument would be required to skip the question. I decided not to add this because, while the redundant question appears silly, it is not much of a bother.

The update command lines (whether for thumb or clone computer) for Doc, Arc, DocPub, Web\Portfolio are all identical except for the directory. They could be processed in a loop to reduce code but then they would have to be done in a group. It is most convenient for the user if directories are processed in order of most to least commonly updated so that routine updating is easily shortened (by ESC). But this group contains a mix of common and rarely updated directories. Instead of a loop with embedded update process, I wrote the update function, reducing each directory’s unique code to little more than the loop approach while enabling complete freedom of order. The function is made sufficiently general to support all cases, including directories not in this group.

In most directories there are many relatively small matching files. Displaying each file’s name would fill up the display with information of little value to the user but we do want to indicate progress. For this, the compx option -OA1 is selected. It just shows a progress dot for each match. Media/Pub is different. It has relatively few, very large files, which can take a long time to compare byte-by-byte, should they have mismatched time stamps. For it, the first (clone) command does not include a -OA argument, thereby selecting the default, which is to show the names of matching files. The gives the user more confidence in the progress than an occasional dot and it doesn’t consume an inordinate amount of display space because there are few files.

TIME STAMPS

By default compx uses a quick file compare, which says that two files are equal if they have the same name (of course), size, and time (last modified). Often, identical files have different time stamps, forcing a potentially long byte-by-byte comparison. If this reveals that they are identical then we can change the time of one of them to match the other, reducing the next script execution time. The -Q argument to compx controls all quick compare options, including whose time stamp gets changed. When updating “this” computer, we typically can’t change anything in the reference computer and must change the clone file time. But when updating a thumb drive we have to change the reference file time stamp because thumb drives use FAT16, which doesn’t support modification time. Consequently, for time stamp change to effectively reduce execution times, thumb drives should be updated first, potentially changing time stamps in the reference computer. Then clone computers are updated, potentially changing their files’ time stamps. This may occasionally (but very rarely) cause a make hiccup.

IMPORTANT TIME NOTE: Windows has a time stamping bug. If a computer’s time zone setting is changed, even just by turning on/off automatic DST, all file time stamps appear changed, forcing a long byte-by-byte comparison on every file. As a general rule, turn off automatic DST adjust on all computers. The reason for this instead of turning it on for all is that the dates of time change are subject to government decision and can change and a computer not on automatic OS updates can change at the wrong time.

DIRECTORY DELETION FAILURE

If there are no file/directory/copy/create problems when updating a clone but a clone directory can’t be deleted in the reverse (pruning) operation, the problem is most likely a hidden file. To test for this, at a command prompt in the offending directory type dir /ah /s. Any hidden files revealed by this can be unhidden (attrib -h filename) but it may still be necessary to reboot to update the file system enough for compx to be able to delete the files. It is much easier to simply delete the directory through Windows Explorer. This could be done under any circumstances but it is best to first determine the problem in order to try to avoid it in the future. If we simply remove by hand any directories that compx can’t remove we will not know the cause.

PRINT FLUSH

Print is to stdout, which is buffered by default. This mixes up the display. Python has added flush argument to print but it defaults to False, forcing every print invocation to include flush = True and it won’t work on an older version of Python. We could call stdout.flush() after every print but I have chosen instead to use a derived file write class.




Prev   Next   Site Map   Home   Top   Valid HTML   Valid CSS