Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

count multiple variables in a single array.

by flieckster (Scribe)
on Feb 26, 2020 at 19:42 UTC ( [id://11113455]=perlquestion: print w/replies, xml ) Need Help??

flieckster has asked for the wisdom of the Perl Monks concerning the following question:

I have a script that goes into a folder on our server, glob's all the files in it, and sends an email to me with exactly what clients files might be in that folder. the files in that folder all have elements in the filenames that i can match to an expression to identify each clients files. i would like to glob the files, then look at the files in the array and match to a list of possible matches and report back to me which client files its found. as you can see with my code below i can match with individual if statements, then list them all out in my email, but that seems wrong, plus i'd rather not have any of the "0" counts show up.
1 TUMI files 0 BW files 0 maurices files
ideally i'd list the files in one email like such. any help in modifying my code to be more scaliabe would be helpful.
1 TUMI file TUMI-1354839054_alt1 1.psd 2 maurices files maur-1111.psd maur-1110.psd
code below::
#!/usr/bin/perl -w use Net::FTP; use File::Copy; use File::Basename; use Email::Send::SMTP::Gmail; use POSIX qw(strftime); my $date = strftime("%m-%d-%y",localtime); my $time = strftime("%I:%M:%S",localtime); my $photo_error_folder = "/Volumes/photorepos/Partners/WorkHorse/ERROR +/PhotographyDrop_ERROR"; my $post_error_folder = "/Volumes/photorepos/Partners/WorkHorse/ERROR/ +PostDrop_ERROR"; my $subject = "ICS -- Files in the Error Folder "; my $dllist ='bflieck@xxx.com'; chdir( $photo_error_folder ) or print "Cant chdir to $photo_error_fold +er $!"; my $count = (@photo_err_list) = glob '"*{jpg,tif,tiff,psd}"'; if ($count > 0) { my ($mail,$error)=Email::Send::SMTP::Gmail->new( -smtp=>'smtp.gmail.co +m', -login=>'kopautomatio +n1@xxx.com', -pass=>'xxx', -layer=> 'ssl', -port=> '465', -debug=> 1, -timeout=> 1000); $tumi_count = grep { /TUMI/ } @photo_err_list; if ($tumi_count > 0) { print "$tumi_count TUMI files\n"; } else { print "$tumi_count TUMI files\n"; } $bw_count = grep { /BW/ } @photo_err_list; if ($bw_count > 0) { print "$bw_count BW files\n"; } else { print "$bw_count BW files\n"; } $maurices_count = grep { /Maur/ } @photo_err_list; if ($maurices_count > 0) { print "$maurices_count Maurices files\n"; } else { print "$maurices_count maurices files\n"; } my $body="please fix and redrop or delete the<br> file://$photo_error_folder/<br><br> "; my $spacer ="<br>"; my $body1= join "<br>\n", @photo_err_list; $mail->send(-to=>"$dllist", -from=>"$dllist", -subject=>"$subject", -b +ody=>"$body $spacer $body1", -contenttype=>"text/html"); $mail->bye; }

Replies are listed 'Best First'.
Re: count multiple variables in a single array.
by 1nickt (Canon) on Feb 26, 2020 at 20:41 UTC

    Hi, I think you might do better looping through the filenames once and building up a hash keyed by the user. Then just print values (file list) for each user that exists in the hash.

    Hope this helps!


    The way forward always starts with a minimal test.
Re: count multiple variables in a single array.
by Marshall (Canon) on Feb 27, 2020 at 06:30 UTC
    It is a bit hard for me to understand your complete specification. However, here is an attempt and some advice.

    I would not as a first thought change into the directory (chdir). Instead, I would run glob on the target directory from the current program directory. You have the full path information to do that. The problem with chdir is that you have to keep track of "where you are". In a longer program this can become problematic, especially in the case of some path error.

    You seem to have a file naming convention for files in this error directory with "userName-fileName.extn". I would not overly specify the .extns that could be in this directory. I allow any extension below (tiff, psd, whatever).

    I would of course not "hard code" the user names, which I think is part of your question. I use a HoA (Hash of Array) below. I made a few files underneath a test directory on my Windows machine, keyed to each discovered user name.

    A long time ago, I would have recommended against using glob() because there were 3 distinct versions and it wouldn't be clear which version you have. Now things have become more standardized and I think glob() is fine.

    use strict; use warnings; use Data::Dumper; my $path ='c:/test'; my %files_per_user; #HOA foreach my $file (glob("$path/*")) { next unless -f $file; # skip directories # if any exist my ($user,$detail_name) = split ('-',$file,2); push @{$files_per_user{$user}},$detail_name; } print Dumper \%files_per_user; foreach my $user (sort keys %files_per_user) { print "",(split("/",$user))[-1],"\n"; print " $_\n" for @{$files_per_user{$user}}; } __END__ What the TEST Directory looks like: Directory of C:\test 02/26/2020 09:52 PM <DIR> . 02/26/2020 09:52 PM <DIR> .. 02/26/2020 09:22 PM 0 maur-1110.tiff 02/26/2020 09:21 PM 0 maur-1111.psd 08/05/2019 01:14 AM <DIR> subdirtest 02/26/2020 09:20 PM 0 TUMI-1354839054_alt1_.psd 3 File(s) 0 bytes $VAR1 = { 'c:/test/TUMI' => [ '1354839054_alt1_.psd' ], 'c:/test/maur' => [ '1110.tiff', '1111.psd' ] }; TUMI 1354839054_alt1_.psd maur 1110.tiff 1111.psd
      Thank you, i think your correct in changing directories it does get very confusing, but sometimes a necessary evil, but i do like the approach. this does seem to work best just to dice up the filenames into prefix and filenames. ultimately this all is going to be sent using  use Email::Send::SMTP::Gmail; what would be your advice for the best way to catch the output of the print commands fo storage to become part of the body of the email?
        It's a good idea to separate the output from the logic if you can, especially if that output is HTML. Consider this:
        use Modern::Perl; use Email::Send::SMTP::Gmail; use POSIX qw(strftime); use File::Find::Rule; use Template; my $subject = "ICS -- Files in the Error Folder"; my $email = 'bflieck@xxx.com'; my @bad_files = File::Find::Rule ->file() ->relative ->maxdepth( 1 ) ->name( '*.jpg', '*.tif', '*.tiff', '*.psd' ) ->in( '/Volumes/photorepos/Partners/WorkHorse/ERROR/PhotographyDro +p_ERROR' ); # Group files per user my %bad_files_per_user; foreach my $file ( @bad_files ) { my $user = (split /-/, $file)[0]; $bad_files_per_user{ $user } = $file; } # Prepare the email text my $body = Template->new->process( \*DATA, { subject => $subject, files => \%bad_files_per_user, date => strftime( "%m-%d-%y", localtime ), time => strftime( "%I:%M:%S", localtime ), }); # Prepare sender my ( $sender, $error ) = Email::Send::SMTP::Gmail->new ( -subject => $subject, -to => $email, -from => $email, -body => $body, # Better to put these in some kind of configuration file # -debug => 1, -smtp => 'smtp.gmail.com', -login => 'kopautomation1@xxx.com', -pass => 'xxx', -layer => 'ssl', -port => '465', -timeout => 1000 ); die "session error: $error" unless ref $sender; # Off we go $sender->send; $sender->bye; __DATA__ <h1>[% subject %]</h1> <p class="date">[% date %] - [% time %]</p> <p>Please fix and redrop or delete the following errors</p> <div class="bad_format"> <h2>Files with bad format</h2> [% FOREACH user IN files.keys %] <h3>User: [% user %] ([% files.$user.size %] files)</h3> <ul class="files"> [% FOREACH file IN files.$user %] <li>[% file %]</li> [% END %] </ul> [% END %] </div>
        I am using the Template Toolkit and a template stored in the __DATA__ section of the script to generate the mail. Because the output is now separated from the code, I (you) can make the email arbitrarily complex (like adding images or styles or whatnot) without having to change or clutter the the code with print statement.

        Also, I am sure you know this already, whitespace is <s>cheap</s> free!


        holli

        You can lead your users to water, but alas, you cannot drown them.
        …changing directories it does get very confusing, but sometimes a necessary evil…

        I am open to being shown incorrect but I don’t think it’s ever necessary and in every case I have come across it so far—including some of my own older stuff—I find it to be code smell.

        Yes, chdir() does have its place.

        "advice for the best way to catch the output of the print commands fo storage to become part of the body of the email?
        You could use sprintf() which makes a string?

        I am not sure whether these user names like "Maur" are actual users on your unix machine? If so, I would use simple SendMail to that user ID and leave it to the users to have a .forward file in their account that forwards to gmail. Don't create a mapping file of some userIDname to gmail address unless for some reason you have to. This adds an extra layer of admin hassle for you.

Re: count multiple variables in a single array.
by AnomalousMonk (Archbishop) on Feb 26, 2020 at 21:36 UTC

    As an example of what 1nickt suggested, maybe something like:

    c:\@Work\Perl\monks>perl -wMstrict -MData::Dump -e "my @files = qw( TUMI-1354839054_alt1 1.psd maur-1111.psd Sam-I-Am.gif maur-1110.psd ); ;; my %user_tag_title = qw(TUMI TUMI BW BW maur maurices); ;; my ($rx_user_search) = map qr{ $_ }xms, join ' | ', map quotemeta, reverse sort keys %user_tag_title ; print $rx_user_search, qq{\n}; ;; my %files_seen; FILE: for my $file (@files) { next FILE unless my ($user) = $file =~ m{ ($rx_user_search) }xms; push @{ $files_seen{$user} }, $file; } dd \%files_seen ;; USER: for my $user (sort keys %user_tag_title) { next USER unless exists $files_seen{$user}; ;; printf qq{%d $user_tag_title{$user} files \n%s \n\n}, scalar(@{ $files_seen{$user} }), join qq{\n}, @{ $files_seen{$user} }; } " (?msx-i: maur | TUMI | BW ) { TUMI => ["TUMI-1354839054_alt1"], maur => ["maur-1111.psd", "maur-1110.psd"], } 1 TUMI files TUMI-1354839054_alt1 2 maurices files maur-1111.psd maur-1110.psd
    For a discussion of how  $rx_user_search is built, see haukex's Building Regex Alternations Dynamically.


    Give a man a fish:  <%-{-{-{-<

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11113455]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (1)
As of 2024-04-24 15:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found