#!/usr/bin/perl # dirdiff.pl -- files diff for two directories. use strict; use warnings; use diagnostics; use File::Find qw(find); use Getopt::Long; use Pod::Usage; use Cwd qw(abs_path); use Sort::Naturally; our $VERSION = '0.05'; my @opt_exts; GetOptions( 'debug=i' => \( my $debug = 0 ), 'help|?' => \( my $opt_help ), 'man' => \( my $opt_man ), 'version' => \( my $opt_version ), 'aonly' => \( my $opt_aonly ), 'bonly' => \( my $opt_bonly ), 'inter' => \( my $opt_inter ), 'jonly' => \( my $opt_jonly ), 'plain' => \( my $opt_plain ), 'order' => \( my $opt_order ), 'ext=s' => \@opt_exts, ) or pod2usage(2); if ($opt_version) { print "$0 vrs. $VERSION\n"; exit; } pod2usage(1) if $opt_help; pod2usage( -verbose => 2 ) if $opt_man; pod2usage("$0: No paths given.") unless @ARGV; @opt_exts = split ( /,/, join ( ',', @opt_exts ) ) if @opt_exts; push ( @ARGV, '.' ); my @dir1 = scandir( $ARGV[0], @opt_exts ); my @dir2 = scandir( $ARGV[1], @opt_exts ); if ( not( $opt_bonly or $opt_jonly ) ) { print_diff( '<', abs_path( $ARGV[0] ), diff( \@dir1, \@dir2 ) ); } if ( not( $opt_aonly or $opt_jonly ) ) { print_diff( '>', abs_path( $ARGV[1] ), diff( \@dir2, \@dir1 ) ); } if ( $opt_inter or $opt_jonly ) { print_diff( '=', '', isect( \@dir1, \@dir2 ) ); } sub print_diff { my ( $decor, $dir, @filename ) = @_; my $prefix = ''; $prefix .= "$decor " if $opt_plain; $prefix .= "$dir" if defined $dir and length $dir; # awkward :-( print map "$prefix$_\n", $opt_order ? nsort(@filename) : @filename; } sub scandir { my ( $dir, @extensions ) = @_; my @dirlist; find( sub { return if /^\.$/ or /^\.\.$/; if (@extensions) { return unless matched( $_, @extensions ); } push ( @dirlist, substr( $File::Find::name, index( $File::Find::name, '/' ) ) ); }, $dir ); return @dirlist; } sub matched { my $pat = shift; my @pats = @_; return grep $pat =~ /$_$/, @pats; } sub diff { my $a = shift; my $b = shift; my %seen; my @aonly; @seen{@$b} = (); for (@$a) { push ( @aonly, $_ ) unless exists $seen{$_}; } return @aonly; } sub isect { my $a = shift; my $b = shift; my %union; my %isect; for ( @$a, @$b ) { $union{$_}++ && $isect{$_}++; } return keys %isect; } __END__ =head1 NAME dirdiff - files diff for two directories. =head1 SYNOPSIS dirdiff [options] path1 [path2] Options: --debug set debug level, default is off --help brief help message --man full documentation --version version number --aonly files in a not in b only --bonly files in b not in a only --inter files in a and b --jonly files in a and b only --plain display result with out prefix; '>', '<' or '=' --order sorted display of result --ext allowed file extensions Switches that don't define a value can be done in long or short form. eg: dirdiff --man dirdiff -m =head1 OPTIONS =over 8 =item B<--debug> Display debug information as program is executed. Control is set by level of the value passed on the command line. Default value is off (debug == 0). =item B<--help> Print a brief help message and exit. =item B<--man> Print the manual page (full documentation) and exit. =item B<--version> Print the version number and exit. =item B<--aonly> Print files in directory a not in directory b. =item B<--bonly> Print files in directory b not in directory a. =item B<--inter> Print files jointly in both directories (intersection of directory a and directory b). =item B<--jonly> Print files jointly in both directories only. =item B<--plain> Print results free of decoration, i.e. no '>', '<' or '='. =item B<--order> Sort level. Defaults to the sort of no sort. Using this options will sort the results in ascending order. =item B<--ext> Allows for specification of file extension to limit result. May be used multiple times or with a comma separated list. =back =head1 DESCRIPTION dirdiff is a simple script to show a files diff for two directories. Similar to the real 'diff', things in directory a are prefixed with a '<'. Things in directory b are prefixed with a '>' and things in both directories are prefixed with a '='. The prefix may be removed with the --plain option. Options exist to display results by themselves or in combination. The default setting is to display files in a, not in b, and files in b, not in a. To add the intersection of a and b, use the --inter option. Note that the default entry for the path2 is '.'. =head1 AUTHOR Hugh S. Myers hsmyers@sdragons.com =head1 BUGS None that I know of. =head1 TODO 0. Edit POD. ONGOING. 1. Add some mechanism for wild card specification. DONE. 2. Add --stats to display date and size information with diff accordingly. 3. Add --order to allow sorted output. DONE. =head1 UPDATES 1. Removed File::Spec::Functions in favor of Cwd, which has the virture of working! 2. Added refactor ala Aristotle---thank you sir! 3. Added --ext to allow specifying extensions. 4. Added --order for sorted output. 5. Added use Sort::Naturally. 6. Aristotle refactors his refactor---thanks again! 7. Restored --order that my implementation of #6. stepped on! =cut