Version 3 of Directory Tree Synchronization

Updated 2004-11-23 23:41:17

George Peter Staplin: I keep some backups on a 2nd hard disk, as a sort of poor-man's RAID/backup (in addition to CD-ROM backups). I use the following tool to synchronize directory trees.

Revision History

Aug 31, 2004: the first version

Nov 23, 2004: new -nocase flag and better/simpler implementation overall

Syntax

$ sync_trees.tcl

syntax: ?-nocase? tree-a tree-b

The -nocase flag is useful for synchronizing a FAT (caseless) file system with a unix file system that is case sensitive.


 #!/usr/bin/env tclsh8.4
 #Copyright 2004 (c) George Peter Staplin

 proc build.tree.stat.info {ar_ptr dir} {
  upvar $ar_ptr ar
  array set ar {}
  foreach f [glob -nocomplain [file join $dir *]] {
   if {[file isdirectory $f]} {
    build.tree.stat.info ar $f
   } elseif {[file isfile $f]} {
    set ar($f) [file size $f]
   }
  }
 }

 proc copy.from.to {from to} {
  puts "COPYING $from $to"
  file mkdir [file dirname $to]
  file copy -force $from $to
 }

 proc sync.tree {a_ptr FROM_DIR b_ptr TO_DIR} {
  upvar $a_ptr a
  upvar $b_ptr b

  foreach {f size} [array get a] {
   if {![info exists b($f)]} {
    copy.from.to [file join $FROM_DIR $f] [file join $TO_DIR $f]
   } elseif {$size != [set b($f)]} {
     puts stderr "The size for $f in $FROM_DIR doesn't match $TO_DIR."
   }
  }
 }

 proc sync.tree.caseless {from FROM_DIR to TO_DIR} {
  foreach {f size} $from {
   set do_copy 1
   foreach {tof tosize} $to {
    if {[string equal -nocase $f $tof]} {
     set do_copy 0
     break
    }
   }
   if {$do_copy} {
    copy.from.to [file join $FROM_DIR $f] [file join $TO_DIR $f]
   } elseif {$tosize != $size} {
    puts stderr "The size for $f in $FROM_DIR doesn't match $TO_DIR."
   }
  }
 }

 proc syntax {} {
  puts stderr "syntax: ?-nocase? tree-a tree-b"
 }

 proc main {argc argv} {

  set caseless 0

  switch -- $argc {
   2 {
    foreach {tree_a tree_b} $argv {}
   }

   3 {
    foreach {flag tree_a tree_b} $argv {}
    if {![string match -nocase -noc* $flag]} {
     syntax
     return 1
    }
    set caseless 1
   }

   default {
    syntax
    return 1
   } 
  }

  set oldwd [pwd]
  cd [set DIR_A [file normalize $tree_a]]
  build.tree.stat.info a {}

  cd $oldwd
  cd [set DIR_B [file normalize $tree_b]]
  build.tree.stat.info b {}

  if {$caseless} {
   sync.tree.caseless [array get a] $DIR_A [array get b] $DIR_B
   sync.tree.caseless [array get b] $DIR_B [array get a] $DIR_A
  } else {
   sync.tree a $DIR_A b $DIR_B
   sync.tree b $DIR_B a $DIR_A
  }
  return 0
 }
 exit [main $::argc $::argv]

Category Application | Category Dev. Tools