A simple script which will process all the mbox files beneath a given directory root and show those which contain unread mail.
#!/usr/bin/perl -w =head1 NAME unread-mail - Display mbox files which contain unread messages. =head1 SYNOPSIS unread-mail [options] Help Options: --help Show this scripts help information. --manual Read this scripts manual. --version Show the version number and exit. --verbose Show verbose information useful for debugging problems. General Options: --dir Specify the root mail directory to search beneath --recent Only process mbox files modified in the most recent N da +ys. --exclude Comma-seperated list of mbox files to exclude from proce +ssing. =cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ $Id: unread-mail,v 1.6 2006/05/28 21:50:13 steve Exp $ =cut =head1 LICENSE Copyright (c) 2005 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use File::Find; use Getopt::Long; use Mail::MboxParser; use Pod::Usage; # # Global options # my %CONFIG; # # Defaults # # Mail directory $CONFIG{'maildir'} = $ENV{'HOME'} . "/mail" if ( -d $ENV{'HOME'} . "/m +ail" ); $CONFIG{'maildir'} = $ENV{'HOME'} . "/Mail" if ( -d $ENV{'HOME'} . "/M +ail" ); # # Exclusion list $CONFIG{'exclude'} = "backup,build,logs,sent,pyzor,spam"; # # Number of days. $CONFIG{'time'} = 2; # # Verbosity $CONFIG{'verbose'} = 0; # # Parse the arguments. # parseCommandLineArguments(); # # Show unread mail # findUnreadMail( $CONFIG{'maildir'} ); # # Finished # exit; =head2 parseCommandLineArguments Process the command line arguments. =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; my $VERSION = 0; # Parse options. # GetOptions( "dir=s", \$CONFIG{'maildir'}, "exclude=s", \$CONFIG{'exclude'}, "recent=s", \$CONFIG{'time'}, "help", \$HELP, "manual", \$MANUAL, "version", \$VERSION, "verbose", \$CONFIG{'verbose'} ); pod2usage(1) if $HELP; pod2usage(-verbose => 2 ) if $MANUAL; if ( $VERSION ) { my $REVISION = '$Revision: 1.6 $'; if ( $REVISION =~ /1.([0-9.]+) / ) { $REVISION = $1; } print "unread-mail CVS: $REVISION\n"; exit; } if ( $CONFIG{'verbose'} ) { print "Configuration:\n"; foreach my $key ( sort keys( %CONFIG ) ) { print $key . " => '" . $CONFIG{$key} . "'\n"; } } } =head2 wanted Called as a result of the File::Find module. Process every given file beneath the $CONFIG{'maildir'} root directo +ry and look for unread mail. Mailboxes contained in the $CONFIG{'exclude'} setting are ignored. =cut sub wanted { my $file = $File::Find::name; # # We only care about files. # return if ( -d $file ); # # Skip some mailboxes. # foreach my $exclude ( split( /,/, $CONFIG{'exclude'} ) ) { if ( $file =~ /$exclude/i ) { $CONFIG{'verbose'} && print "Excluded mail file: $file\n"; return; } } # # Skip files if they've not been modified too recently. # my $time = -M $file; if ( defined( $time ) ) { if ( $time > $CONFIG{'time'} ) { $CONFIG{'verbose'} && print "Mail file not modified recently: +$file\n"; return; } } else { $CONFIG{'verbose'} && print "Failed to stat: $file - $!\n"; return; } my $parseropts = { enable_cache => 1, enable_grep => 1, cache_file_name => '/tmp/mail-cache', }; my $mb = Mail::MboxParser->new( $file, decode => 'NONE', parseropts => $parseropts ); my $count = 0; for my $msg ($mb->get_messages) { my $status = $msg->header->{status}; if ( !defined( $status ) ) { $count += 1; } } if ( $CONFIG{'verbose'} ) { print "$file ($count)\n"; } else { print "$file ($count)\n" if ($count > 1 ); } } =head2 findUnreadMail Look for files and directories beneath the maildir root and call 'wanted' on them. =cut sub findUnreadMail { my $dir = $CONFIG{'maildir'}; die "$CONFIG{'maildir'} doesn't exist" unless ( -d $CONFIG{'maildi +r'} ); $CONFIG{'verbose'} && print "Staring search for unread mail in: $d +ir\n"; # # Do the searching. # find( { wanted => \&wanted, no_chdir => 1 }, $dir ); $CONFIG{'verbose'} && print "Finished search for unread mail in: $ +dir\n"; }
Steve
--

Replies are listed 'Best First'.
Re: Show mbox files with unread mail messages in them
by jwkrahn (Abbot) on May 29, 2006 at 03:10 UTC
    AFAIK there is no metadata in an mbox file that determines if a particular message has been read or not? Please correct me if I am wrong.

    The first thing I did was to run the podchecker program on yours and it produced two syntax errors.

    Some comments:

    159 sub wanted 160 { 161 my $file = $File::Find::name; 162 163 # 164 # We only care about files. 165 # 166 return if ( -d $file );

    I guess you are assuming that there are no named pipes or FIFOs or other types of files?

    167 168 # 169 # Skip some mailboxes. 170 # 171 foreach my $exclude ( split( /,/, $CONFIG{'exclude'} ) )

    What if you have a file name with a comma in it?

    172 { 173 if ( $file =~ /$exclude/i )

    What if you have a file name with a regex meta-character in it? What if the contents of $exclude are a subset of a larger file name? $file includes the complete path so $exclude could match on one of the path names. What if you want to exclude 'file' but not 'FILE'?

    174 { 175 $CONFIG{'verbose'} && print "Excluded mail file: $ +file\n"; 176 return; 177 } 178 } 179 180 181 # 182 # Skip files if they've not been modified too recently. 183 # 184 my $time = -M $file; 185 if ( defined( $time ) ) 186 { 187 if ( $time > $CONFIG{'time'} ) 188 { 189 $CONFIG{'verbose'} && print "Mail file not modifie +d recently: $file\n"; 190 return; 191 } 192 } 193 else 194 { 195 $CONFIG{'verbose'} && print "Failed to stat: $file - $ +!\n";

    You report an error if you cannot stat the file but not at line 166?

    196 return; 197 }

    HTH

    Edit: g0n - moved comments out of code tags

      AFAIK there is no metadata in an mbox file that determines if a particular message has been read or not?

      If you're using Mutt like me then a new header is added 'Status: RO' for "read" and "old" when a message is read. This may be something other programs add, or it may be mutt-specific. I'm not sure.

      I guess you are assuming that there are no named pipes or FIFOs or other types of files?

      Yes, but I think that is a valid assumption in this case.

      I think your comments on the exclusion of filenames being very simplistic are very valid, but for simple code it works well for me. I guess I'm lucky that I don't have any meta-characters in my mailbox names.

      I've updated the node to fix the pod errors you pointed out, so thanks for those.

      Steve
      --