Recently I came across the need to see the difference between two directories. After a bit of hacking I produced dirdiff! From the pod:
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 '.'.
No doubt there is a better unix solution, but I don't live there so to speak!
#!/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 le +vel 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 a +nd 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 use +d multiple times or with a comma separated list. =back =head1 DESCRIPTION dirdiff is a simple script to show a files diff for two directories. S +imilar 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 wi +th a '='. The prefix may be removed with the --plain option. Options exist to displa +y 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, us +e 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 accordi +ngly. 3. Add --order to allow sorted output. DONE. =head1 UPDATES 1. Removed File::Spec::Functions in favor of Cwd, which has the virtu +re 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

Update 1. Added Aristotle's suggestion/refactor.
Update 2. Added --order and --ext.
Update 3. Added Sort::Naturally and Aristotle's refactor to his refactor.
Update 4. Fix stepping on self with previous update!

--hsm

"Never try to teach a pig to sing...it wastes your time and it annoys the pig."

Replies are listed 'Best First'.
Re: A directory diff
by Aristotle (Chancellor) on Aug 25, 2004 at 21:40 UTC

    Nice work, overall. The bit in the middle violates Don't Repeat Yourself, though.

    sub print_diff { my ( $decor, $dir, @filename ) = @_; my $prefix = ( ( $opt_plain ? "$decor " : '' ) . ( defined $dir ? "$dir " : '' ) ); print map "$prefix$_\n", @filename; } if ( not( $opt_bonly or $opt_jonly ) ) { print_diff '<', $dirname1, diff( \@dir1, \@dir2 ); } if ( not( $opt_aonly or $opt_jonly ) ) { print_diff '>', $dirname2, diff( \@dir2, \@dir1 ); } if ( $opt_inter or $opt_jonly ) { print_diff '=', '', isect( \@dir1, \@dir2 ); }

    Makeshifts last the longest.

      Nice touch! Thanks for lending me your expertise---I'll add it to an update.

      --hsm

      "Never try to teach a pig to sing...it wastes your time and it annoys the pig."

        Btw, I just noticed that if you happen to pass neither decoration nor directory, $prefix will be undefined and you'll get warnings. Also, the sub is testing definedness of $dir, but the main program is passing an empty string in the last call — doesn't match up. All that considered, the sub should probably read

        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", @filename; }

        That's what I get for not testing my code. :-)

        Makeshifts last the longest.

Re: A directory diff
by belg4mit (Prior) on Aug 25, 2004 at 21:33 UTC
    This is really only doing a diff of ls -1, what about the files themselves, they may of different sizes etc. See below for a four year-old script. Mind you, that's from a long time ago.

    --
    I'm not belgian but I play one on TV.

      Did you see the remark about not using unix? I'm not so lucky as you. On the other hand I have planned on adding some mechanism to allow wild card extensions and perhaps something to differentiate date and size---I didn't need it when I wrote it, but it seems like a good idea!

      --hsm

      "Never try to teach a pig to sing...it wastes your time and it annoys the pig."
        I did not see it. There are file attributes in win32 land that you could compare, and stat still does filesize, etc. Finally, I'd probably used Digest::MD5 if I were to rewrite this (which would work in win32). So, while you might not be able to simply download and use this (well, you probably could under cygwin -- which you should have if you enevy those with access to *nix) it was mostly intended to show that there's more to directories than filenames.

        --
        I'm not belgian but I play one on TV.

Re: A directory diff
by sgifford (Prior) on Aug 31, 2004 at 17:38 UTC
    A quick-n-dirty ksh/bash script version of this is:
    diff <(ls -l /dir1) <(ls -l /dir2)
    It doesn't have nearly as many features and the output is hard to read, so it's mostly here as a curiosity, and an example of another possible approach from someone who does live there. :)
      It does seem like a nice place to live, sigh---thanks for the postcard. Maybe I can convince some of my clients to vist!!

      --hsm

      "Never try to teach a pig to sing...it wastes your time and it annoys the pig."

        But you can visit, and for free!

        Cygwin
        You get bash and most other shell environments and utilities at your diposal. It's nice to be at a Win32 command line using ls.

        MS Services for Unix
        I'm personally more partial to Cygwin, but it's free with ksh as the default shell.