As The title suggest, you will create a program that is used to maintain an up-to-date copy (backup?) of files you are working on. Any files in the work directory tree that are modified or new will be copied to the destination tree replacing any files already there.
Note: The user must delete files from both trees.
A Perl version of my maintenance/backup program is below. My Perl code may be used as a resource/template/design/... for your program.
Create a program that automatically deletes files in the destination (backup) directory tree that are no longer in the work directory tree.
#!/bin/perl -w #=================================================================== # keep a source directory tree up to date # # 1. If there are files in the source tree that are newer that # the destination tree, replace the destination tree file. # 2. If a file in the source tree does not exist in the destination # tree copy it to the destination tree. # 3. A directory in the skip list (and all of its sub-directories) # will not be processed (skipped). # # Note: This script will not create directories. # #=================================================================== use strict; use FileHandle; use DirHandle; use File::Basename; use File::Copy; #------------------------------------------------------------------- # global variables # # DELBUG debug flag # DIR_P_COUNT count - directories processed # DROOTDIR destination tree root directory # FAILURE returned failure code # FILE_A_COUNT count - files added to destination tree # FILE_P_COUNT count - source files processed # FILE_U_COUNT count - files updated in destination tree # FORCEMODE force the copy of all files (don't test for latest) # RDIRLIST a list of all of the directories in the source tree # (relative to the source tree root directory) # SKIPDIRS a list of directories to skip # SROOTDIR source tree root directories # STARTTIME script start time # SUCCESS returned success code # TESTMODE test mode flag # VERBOSE verbose flag # #------------------------------------------------------------------- # -- work computer # -- copy files from the flash drive to C drive (desktop) #my $SROOTDIR = 'U:/Secure-Web-Pages-Class'; # flash drive #my $DROOTDIR = 'C:/Secure_Web_Pages-Class'; # C drive # -- work computer # -- copy files from C drive (desktop) to flash drive #my $SROOTDIR = 'C:/Secure-Web-Pages-Class'; # C drive #my $DROOTDIR = 'U:/Secure-Web-Pages-Class'; # flash drive # -- home computer # -- copy files from the flash drive to the C drive #my $SROOTDIR = 'L:/Secure-Web-Pages-Class'; #my $DROOTDIR = 'C:/Secure-Web-Pages-Class'; # -- home computer # -- copy files from the C drive to the flash drive #my $SROOTDIR = 'C:/Secure-Web-Pages-Class'; #my $DROOTDIR = 'L:/Secure-Web-Pages-Class'; # -- copy files from work computer to AFS space #my $SROOTDIR = 'C:/Secure-Web-Pages-Class'; # C drive #my $DROOTDIR = 'T:/www/Secure-Web-Pages-Class'; # AFS # -- copy files from flash drive to AFS space #my $SROOTDIR = 'U:/Secure-Web-Pages-Class'; # flash drive #my $DROOTDIR = 'T:/www/Secure-Web-Pages-Class'; # AFS # directories to be skipped (by name) my %SKIPDIRS = ('x', 1, 'java', 1, 'X', 1, 'instructor', 1, 'zz', 1); # -- files to be skipped (by name) my %SKIPFILENAMES = ('keep_dir_tree_up_to_date.pl', 1, 'Thumbs.db', 1); # files to be skipped (by regexp pattern) # (including expression delimiters) my @SKIPFILEPATTERNS = ('\.vsd$'); #my @SKIPFILEPATTERNS = (); my $DEBUG = 0; my $DIR_P_COUNT = 0; my $FAILURE = 0; my $FILE_A_COUNT = 0; my $FILE_P_COUNT = 0; my $FILE_U_COUNT = 0; my $FORCEMODE = 0; my $MAKEDIRS = 0; my @RDIRLIST = (''); # initial value '' so that the root # source directory will be processed my $STARTTIME = time; my $SUCCESS = 1; my $TESTMODE = 1; my $ADDFILES = 0; my $VERBOSE = 1; #=================================================================== #=================================================================== # main #=================================================================== #=================================================================== #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # make sure the root directories do not end with a / or \ character #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $SROOTDIR =~ s/\/$//; $DROOTDIR =~ s/\/$//; $SROOTDIR =~ s/\\$//; $DROOTDIR =~ s/\\$//; #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # process command line arguments #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (ProcessCommandLineArguments() != $SUCCESS) { exit 1; }; #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # create a list of all of the source tree directories, # skipping the directories in the skip list (and their sub-directories) # the list values are the relative to the source root directory #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ($VERBOSE) { print "\nCreating source tree directory list\n"; } if (CreateSourceDirList($SROOTDIR) != $SUCCESS) { exit 1; } if ($DEBUG > 1) { foreach (@RDIRLIST) { print "Relative source directory ($_)\n"; } } #------------------------------------------------------------------- # verity that there is a destination directory for every # source directory #------------------------------------------------------------------- if ($VERBOSE) { print "\nVerifying source directories have " . "a matching destination directory\n"; } if (VerifyDestDirsExist() != $SUCCESS) { exit 1; } #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # update each destination directory with files from its # corresponding source directory #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ($VERBOSE) { print "\nUpdating destination directories\n"; } my $sdir; # source directory foreach $sdir (@RDIRLIST) { if (UpdateDestDir($sdir) != $SUCCESS) { exit 1; } } #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # display processing statistics #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - print "\n"; print "---------------------------------------------------\n"; print "Source directory : $SROOTDIR\n"; print "Destination directory : $DROOTDIR\n"; print "Test mode : $TESTMODE\n"; print "Make dirs : $MAKEDIRS\n"; print "Directories processed : $DIR_P_COUNT\n"; print "Files processed : $FILE_P_COUNT\n"; print "Files added : $FILE_A_COUNT\n"; print "Files updated : $FILE_U_COUNT\n"; print "----------------------------------------------------\n"; print "\n"; exit 0; #=================================================================== #=================================================================== # subroutines #=================================================================== #=================================================================== #------------------------------------------------------------------- # create a list of all of the source tree directories, # skipping the directories in the skip list # (and their sub-directories) #------------------------------------------------------------------- sub CreateSourceDirList { my $sdir = $_[0]; # source directory my $dh = new DirHandle; # directory handle my $e; # list entry my $f; # file name my @fl; # list of file names my @fs; # sorted list of file names if ($DEBUG) { print "CreateSourceDirList($sdir)\n"; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # get a list of all of the files in the source directory # (this includes other directories) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (! opendir($dh,$sdir)) { print "\nCan't open $sdir: $!\n\n"; return $FAILURE; } @fl = readdir $dh; closedir $dh; # sort the list of file names @fs = sort @fl; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # process each sub-directory in the current directory # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - foreach $e (@fs) { if ($e eq '.') { next; }; if ($e eq '..') { next; }; $f = $sdir . '/' . $e; if (-d $f) { if (defined $SKIPDIRS{$e}) { if ($VERBOSE) { print "\nSkipping directory $e\n"; } next; } $f =~ s/^$SROOTDIR//; # remove the root dir from complete # path and (dir) file name leaving a # relative path and (dir) file name push @RDIRLIST,$f; } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # now process every sub-directory # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - foreach $e (@fs) { if ($e eq '.') { next; }; if ($e eq '..') { next; }; $f = $sdir . '/' . $e; if (-d $f) { if (defined $SKIPDIRS{$e}) { next; } if (CreateSourceDirList($f) != $SUCCESS) { return $FAILURE; } } } return $SUCCESS; } #------------------------------------------------------------------- # verity that there is a destination directory for every # source directory #------------------------------------------------------------------- sub VerifyDestDirsExist { if ($DEBUG) { print "VerifyDestDirsExist()\n"; } my $ddir; # destination directory my $rdir; # relative directory my $sdir; # source directory foreach $rdir (@RDIRLIST) { $sdir = $SROOTDIR . $rdir; $ddir = $DROOTDIR . $rdir; if (! -e $ddir) { if ($MAKEDIRS) { if (mkdir($ddir)) { print "Directory created: $ddir\n"; next; } print "\nError: conable to create directory\n"; print "Dir: $ddir\n"; print "$!\n"; return $FAILURE; } print "\nError: source directory does not " . "have a matching destination directory\n"; print " Source: $sdir\n"; print " Destination: $ddir\n"; return $FAILURE; } } return $SUCCESS; } #------------------------------------------------------------------- # update a destination directory #------------------------------------------------------------------- sub UpdateDestDir { my $rdir = $_[0]; # relative source directory if ($DEBUG) { print "UpdateDestDir($rdir)\n"; } $DIR_P_COUNT++; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # define the source and destination directories from the # relative source directory # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $sdir = $SROOTDIR . $rdir; # source directory my $ddir = $DROOTDIR . $rdir; # destination directory if ($VERBOSE) { print "Processing: $sdir\n"; } if ($VERBOSE == 3) { print "Updating destination: $ddir\n"; print " source : $sdir\n"; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # get a list of all of the files in the source directory # (this includes other directories) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $dh = new DirHandle; # directory handle my $df; # destination file name and path my $e; # list entry my $sf; # source file name and path my @fl; # list of file names my @fs; # sorted list of file names if (! opendir($dh,$sdir)) { print "\nCan't open $sdir: $!\n\n"; return $FAILURE; } @fl = readdir $dh; closedir $dh; # sort the list of file names @fs = sort @fl; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # process each file in the source directory # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $supdate; # source file update date my $dupdate; # destination file update date my $t; # delta time value my $p; # pattern foreach $e (@fs) { if ($e eq '.') { next; }; if ($e eq '..') { next; }; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # special case for Perl editor files # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ($e =~ /^perl5db\.pl$/) { next; } if ($e =~ /\.pltemp\.pl$/) { next; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # create source and destination file (path and name) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $sf = $sdir . '/' . $e; $df = $ddir . '/' . $e; if (-d $sf) { next; } # skip directories $FILE_P_COUNT++; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # skip the file (by name) ? # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (exists $SKIPFILENAMES{$e}) { if ($VERBOSE > 1) { print " S: $sf\n"; } next; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # skip the file by regexp pattern match ? # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (SkipFileByPattern($e)) { next; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # if in force mode, bypass test file update dates and # copy all files to the destination directory # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ($FORCEMODE) { if (! $TESTMODE) { if (copy($sf,$df) != 1) { print "\nError: copying file, $!\n"; print " Src: $sf\n"; print " Dst: $df\n"; return $FAILURE; } } $FILE_A_COUNT++; if ($VERBOSE) { print " A: $sf\n"; } next; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # if the file does not exist in the destination directory, # copy the file to the destination directory # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (! -e $df) { if (! $TESTMODE) { if (copy($sf,$df) != 1) { print "\nError: copying file, $!\n"; print " Src: $sf\n"; print " Dst: $df\n"; return $FAILURE; } } $FILE_A_COUNT++; if ($VERBOSE) { print " A: $sf\n"; } next; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # if the file exist in the destination directory, # and the source file is newer that the destination file, # copy the file to the destination directory # # Note: There is a 2 second fudge factor built into the code. # This will hopefully mask differences between the system # clocks. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $supdate = (stat($sf))[9]; $dupdate = (stat($df))[9]; $t = abs($dupdate - $supdate); if ($t <= 2) { next; } if ($ supdate > $dupdate) { if (! $TESTMODE) { if (copy($sf,$df) != 1) { print "\nError: copying file, $!\n"; print " Src: $sf\n"; print " Dst: $df\n"; print " Src update: $supdate\n"; print " Dst update: $dupdate\n"; return $FAILURE; } } $FILE_U_COUNT++; if ($VERBOSE) { print " U: $sf\n"; } if ($VERBOSE == 3) { print " Src: $supdate\n"; print " Dst: $dupdate\n"; print " Del: $t (sec)\n"; } } } return $SUCCESS; } #------------------------------------------------------------------- # match filename to skip regexp patterns #------------------------------------------------------------------- sub SkipFileByPattern { my $f = $_[0]; # filename my $p; # filename pattern foreach $p (@SKIPFILEPATTERNS) { if ($f =~ /$p/) { return 1; } } return 0; } #------------------------------------------------------------------- # process command line arguments #------------------------------------------------------------------- sub ProcessCommandLineArguments { my $e; # command line argument my $ee; # command line argument (uppercase) for $e (@ARGV) { $ee = $e; $ee =~ tr/a-z/A-Z/; if ($ee eq "DEBUG") { $DEBUG = 1; next; } if ($ee eq "DEBUG1") { $DEBUG = 1; next; } if ($ee eq "DEBUG2") { $DEBUG = 2; next; } if ($ee eq "DEBUG3") { $DEBUG = 3; next; } if ($ee eq "DEBUG4") { $DEBUG = 4; next; } if ($ee eq "FORCE") { $FORCEMODE = 1; next; } if ($ee eq "FORCEMODE") { $FORCEMODE = 1; next; } if ($ee eq "VERBOSE") { $VERBOSE = 1; next; } if ($ee eq "VERBOSE1") { $VERBOSE = 1; next; } if ($ee eq "VERBOSE2") { $VERBOSE = 2; next; } if ($ee eq "VERBOSE3") { $VERBOSE = 3; next; } if ($ee eq "COPY") { $TESTMODE = 0; next; } if ($ee eq "UPDATE") { $TESTMODE = 0; next; } if ($ee eq "NOTEST") { $TESTMODE = 0; next; } } return $SUCCESS; }