#!/usr/bin/perl -w # # $Id: dirdiff,v 1.1 2000/08/09 02:08:10 vogelke Exp $ # # NAME: # dirdiff # # SYNOPSIS: # dirdiff [-v] dir1 dir2 # # DESCRIPTION: # Compare 2 directory hierarchies. A bit like diff -r. # It doesn't try to expand symbolic links, but it will tell # you if 2 symlinks are different. # # It doesn't try to list differences between files, it just # lists which files/dirs are different. It shows which files # have different permissions. It won't try comparing special files, # etc. It doesn't list the full pathname, just the common component. # # Output consists of a list of files, prefixed by one of the # following chars: # # 1 file file only exists in 1st dir # 2 file file only exists in 2nd dir # ! file files differ # @ file files are symbolic links with different values # t file files are of different types (eg reg file/sym link) # p file files are identical, but have different permissions # m file files are identical, but have different modtimes # u file files are identical, but owned by different users # g file files are identical, but owned by different groups # # OPTIONS: # "-v" prints the current version and exits. # # HISTORY: # Originally from the sun-managers mailing list. # (C) 2000 Dave.Mitchell@fdgroup.com # Comments added by Karl Vogel # # Message-ID: # To: "Sun Managers" # Subject: [POST-SUMMARY] Directory Differences # Date: Tue, 8 Aug 2000 15:28:26 -0500 # From: "Zander, Mark" # # Since so many people have asked for this, here it is. # This was written by David Mitchell [davem@fdgroup.co.uk], # all thanks should go to him. # # ----- # Mark Zander Mark.Zander@mezzia.com # Unix System Admin # mezzia e-business # Indianapolis, IN USA # Third Stone From The Sun # http://www.mezzia.com use strict; use subs qw(cmpbyte cmpfile cmpdir exit version usage sigcatcher); use Getopt::Std; use vars qw($opt_v); $ENV{"PATH"} = "/bin:/usr/sbin:/usr/local/bin"; my ($myname); # script basename my ($DIR1); my ($DIR2); #--------------------------------------------------------------------- # Trap common signals. Handle command line arguments (if any). ($myname) = split (/\//, reverse ($0)); $myname = reverse ($myname); # script basename. $SIG{'HUP'} = 'sigcatcher'; $SIG{'INT'} = 'sigcatcher'; $SIG{'QUIT'} = 'sigcatcher'; $SIG{'TERM'} = 'sigcatcher'; usage() unless getopts ("v"); version() if $opt_v; # # Fun starts here. # usage() unless @ARGV == 2; $DIR1 = $ARGV[0]; $DIR2 = $ARGV[1]; die "$DIR1: not a directory\n" unless -d $DIR1; die "$DIR2: not a directory\n" unless -d $DIR2; cmpdir('.'); exit (0); #--------------------------------------------------------------------- # Print a short usage message from the comment header and exit. sub usage { my $emsg = "No usage information"; if (open (P, "$0")) { while (

) { last if /^# NAME:/; } print STDERR "\n NAME:\n"; while (

) { last if /^\s*$/; last if /^# HISTORY:/; s/^#//; print STDERR; } close (P); $emsg = ""; } exit (1, $emsg); } #--------------------------------------------------------------------- # Do something if we get a signal. sub sigcatcher { my ($sig) = @_; exit (2, "caught signal SIG$sig -- shutting down"); } #--------------------------------------------------------------------- # Print the current version and exit. sub version { $_ = '$RCSfile: dirdiff,v $ $Revision: 1.1 $ ' . '$Date: 2000/08/09 02:08:10 $'; s/RCSfile: //; s/.Date: //; s/,v . .Revision: / v/; s/\$//g; print "$_\n"; CORE::exit (0); } #--------------------------------------------------------------------- # Clean up. sub exit { my ($code, $msg); ($code, $msg) = @_; warn "$myname: $msg\n" if $msg; CORE::exit ($code); } #--------------------------------------------------------------------- # cmpbyte; # # compare 2 files byte-by-byte # return 0 if same, 1 if different sub cmpbyte { my ($path) = @_; my ($buf1); my ($buf2); my ($bufsize); my ($r1); my ($r2); $bufsize = 8192; $buf1 = ' ' x $bufsize; $buf2 = ' ' x $bufsize; unless(open(FD1,"$DIR1/$path")) { print STDERR "cant open $DIR1/$path: $!\n"; return 1; } unless(open(FD2,"$DIR2/$path")) { print STDERR "cant open $DIR2/$path: $!\n"; return 1; } for (;;) { $r1 = read(FD1,$buf1,$bufsize); unless(defined($r1)) { print STDERR "error reading $DIR1/$path: $!\n"; return 1; } $r2 = read(FD2,$buf2,$bufsize); unless(defined($r2)) { print STDERR "error reading $DIR2/$path: $!\n"; return 1; } # we assume that if we're reading a regular file, we'll always # be given $bufsize bytes except at eof if ($r1 != $r2) { return 1; } if ($r1 == 0) { return 0; } if ($buf1 ne $buf2) { return 1; } } } #--------------------------------------------------------------------- # cmpfile; # # compare the 2 files $DIR1/$path $DIR2/$path: # do trivial checks like they are different types, different lengths # etc. Failing that, do a byte-by-byte compare. As a last resort, see if # the permissions are different. If they are both dirs, recursively call # cmpdir sub cmpfile { my ($path) = @_; my ($isadir); my ($isafile); my ($isalink); my ($isnot); my ($type1); my ($type2); my (@stat1); my (@stat2); # enumeration types for cmpfile $isafile=0; $isalink=1; $isadir =2; $isnot =3; @stat1 = lstat("$DIR1/$path"); if (-d _) { $type1 = $isadir; } elsif (-l _) { $type1 = $isalink; } elsif (-f _) { $type1 = $isafile; } else { $type1 = $isnot; } @stat2 = lstat("$DIR2/$path"); if (-d _) { $type2 = $isadir; } elsif (-l _) { $type2 = $isalink; } elsif (-f _) { $type2 = $isafile; } else { $type2 = $isnot; } if ($type1 != $type2) { print "t $path\n"; return; } if ($type1 == $isadir){ cmpdir($path); return; } if ($type1 == $isalink) { if (readlink("$DIR1/$path") ne readlink("$DIR2/$path")) { print "@ $path\n"; } return; } # XXX we really aught to do more checks here, eg are they the same # char special device, or what if ($type1 == $isnot) { return; } # they're plain files: see if they match # stat[7] is st_size; if (($stat1[7] != $stat2[7]) || cmpbyte($path)) { print "! $path\n"; return; } # finally, see if the permissions & ownerships are different if ($stat1[4] != $stat2[4]) { print "u $path\n"; return; # differing UIDs } if ($stat1[5] != $stat2[5]) { print "g $path\n"; return; # differing GIDs } if ($stat1[9] != $stat2[9]) { print "m $path\n"; return; # differing mtimes } } #--------------------------------------------------------------------- # cmpdir; # # recusrsively compare 2 directories, whose paths are # $DIR1$path, $DIR2$path # it is assumed that both these are valid directories sub cmpdir { my ($path) = @_; my ($diff); my ($file1); my ($file2); my ($i); my ($path1); my ($path2); my (@dir1); my (@dir2); $path1 = "$DIR1/$path"; $path2 = "$DIR2/$path"; # suck in the 2 directory contents unless (opendir(DIR,$path1)) { print STDERR "Couldnt open $path1\n"; return; } @dir1 = readdir(DIR); closedir(DIR); splice(@dir1,0,2); # remove . and .. @dir1 = sort(@dir1); unless (opendir(DIR,$path2)) { print STDERR "Couldnt open $path2\n"; return; } @dir2 = readdir(DIR); closedir(DIR); splice(@dir2,0,2); # remove . and .. @dir2 = sort(@dir2); closedir(DIR); # see if the directories differ $diff=0; if ($#dir1 == $#dir2) { for ($i=0; $i<=$#dir1; $i++) { if ($dir1[$i] ne $dir2[$i]) { $diff=1; last; } } } else {$diff = 1;} if ($diff) { print "! $path/\n"; } while (@dir1 && @dir2) { $file1 = shift(@dir1); $file2 = shift(@dir2); if ($file1 lt $file2) { print "1 $path/$file1\n"; unshift(@dir2,$file2); next; } elsif ($file1 gt $file2) { print "2 $path/$file2\n"; unshift(@dir1,$file1); next; } cmpfile("$path/$file1"); } # list any trailing non-common filenames while(@dir1) { $file1 = shift(@dir1); print "1 $path/$file1\n"; } while(@dir2) { $file2 = shift(@dir2); print "2 $path/$file2\n"; } }