tags:

views:

164

answers:

5

This is what my Perl code looks like for monitoring a Unix folder :

#!/usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions;

my $date    = `date`; chomp $date;
my $datef   = `date +%Y%m%d%H%M.%S`; chomp $datef;
my $pwd     = `pwd`; chomp $pwd;

my $cache   = catfile($pwd, "cache");
my $monitor = catfile($pwd, "monme");
my $subject = '...';
my $msg     = "...";
my $sendto  = '...';
my $owner   = '...';

sub touchandmail {
     `touch $cache -t "$datef"`;
     `echo "$msg" | mail -s "$subject" $owner -c $sendto`;
}

while(1) {

    $date  = `date`; chomp $date;
    $datef = `date +%Y%m%d%H%M.%S`; chomp $datef; 

    if (! -e "$cache") {
        touchandmail();
    } elsif ("`find $monitor -newer $cache`" ne "") {
        touchandmail();
    }
    sleep 300;
}
  • To do a chomp after every assignment does not look good. Is there some way to do an "autochomp"?

  • I am new to Perl and might not have written this code in the best way. Any suggestions for improving the code are welcome.

+5  A: 

Try putting it into a function:

sub autochomp {
    my $command = shift;
    my $retval = `$command`;
    chomp $retval;
    return $retval;
}

And then call that for each command you want to execute and then chomp.

Nathan Fellman
+4  A: 

Use DateTime or other of the date modules on CPAN instead of the date utility.

For example:

use DateTime;

my $dt = DateTime->now;
print $dt->strftime('%Y%m%d%H%M.%S');
jira
For just the date `POSIX` is potentially even easier/lighter: `use POSIX qw/strftime/; my $date = strftime "%Y%m%d%H%M.%S", localtime;`
Telemachus
+2  A: 

It is possible to assign and chomp in a single line using the following syntax:

chomp ( my $date = `date` );

As for speaking more Perlishly, if you find yourself repeating the same thing over and over again, roll it into a sub:

sub assign_and_chomp {

    my @result;
    foreach my $cmd (@_) {
        chomp ( my $chomped = $cmd );
        push @result, $chomped;
    }
    return @result;
}

my ( $date , $datef , $pwd )

   = assign_and_chomp ( `date` , `date +%Y%m%d%H%M.%S` , `pwd` );
Zaid
[`chomp`](http://perldoc.perl.org/functions/chomp.html) “returns the total number of characters removed from all its arguments,” not the chomped results.
Greg Bacon
@gbacon : That's why I'm `chomp`ing scalars one by one
Zaid
@Zaid But your loop pushes values returned from `chomp` onto `@result`.
Greg Bacon
@FM : Thanks for the edit.
Zaid
Careful: if you chomp and assign a single scalar in the test portion of a `while` loop or `if` clause, you may not get what you expect...
drewk
+12  A: 

Don't use the shell, then.

#! /usr/bin/perl

use warnings;
use strict;

use Cwd;
use POSIX qw/ strftime /;

my $date    = localtime;
my $datef   = strftime "%Y%m%d%H%M.%S", localtime;
my $pwd     = getcwd;

The result is slightly different: the output of the date command contains a timezone, but the value of $date above will not. If this is a problem, follow the excellent suggestion by Chas. Owens below and use strftime to get the format you want.

Your sub

sub touchandmail {
  `touch $cache -t "$datef"`;
  `echo "$msg" | mail -s "$subject" $owner -c $sendto`;
}

will fail silently if something goes wrong. Silent failures are nasty. Better would be code along the lines of

sub touchandmail {
  system("touch", "-t", $datef, $cache) == 0
    or die "$0: touch exited " . ($? >> 8);

  open my $fh, "|-", "mail", "-s", $subject, $owner, "-c", $sendto
    or die "$0: could not start mail: $!";

  print $fh $msg
    or warn "$0: print: $!";

  unless (close $fh) {
    if ($! == 0) {
      die "$0: mail exited " . ($? >> 8);
    }
    else {
      die "$0: close: $!";
    }
  }
}

Using system rather than backticks is more expressive of your intent because backticks are for capturing output. The system(LIST) form bypasses the shell and having to worry about quoting arguments.

Getting the effect of the shell pipeline echo ... | mail ... without the shell means we have to do a bit of the plumbing work ourselves, but the benefit—as with system(LIST)—is not having to worry about shell quoting. The code above uses many-argument open:

For three or more arguments if MODE is '|-', the filename is interpreted as a command to which output is to be piped, and if MODE is '-|', the filename is interpreted as a command that pipes output to us. In the two-argument (and one-argument) form, one should replace dash ('-') with the command. See Using open for IPC in perlipc for more examples of this.

The open above forks a mail process, and $fh is connected to its standard input. The parent process (the code still running touchandmail) performs the role of echo with print $fh $msg. Calling close flushes the handle's I/O buffers plus a little extra because of how we opened it:

If the filehandle came from a piped open, close returns false if one of the other syscalls involved fails or if its program exits with non-zero status. If the only problem was that the program exited non-zero, $! will be set to 0. Closing a pipe also waits for the process executing on the pipe to exit—in case you wish to look at the output of the pipe afterwards—and implicitly puts the exit status value of that command into $? and ${^CHILD_ERROR_NATIVE}.

Greg Bacon
@gbacon: thanks for the touchandmail suggestions. I was thinking of incorporating some error handling for it.
Lazer
@gbacon: what is `"|-"`?
Lazer
@Lazer: `|-` opens a pipe and redirects everything written to the filehandle to the new process's stdin.
Nathan Fellman
`strftime "%a %b %d %H:%M:%S %Z %Y", localtime` may or may not work as a replacement for `date`. The problem is that `%Z` is reliant on the system for the timezone name.
Chas. Owens
Using the shell is more computationally expensive anyway. Launching a new process every time you check the date is a bit much.
fennec
+5  A: 

More generally, the IO::All module does indeed provide the equivalent of an autochomp:

use IO::All;
# for getting command output:
my @date = io("date|")->chomp->slurp;
#$date[0] contains the chomped first line of the output

or more generally:

my $fh = io("file")->chomp->tie;
while (<$fh>) {
 # no need to chomp here !  $_ is pre-chomped
}

Granted, for this particular case of date I would agree with the other answerers that you are probably better off using one of the DateTime modules, but if you are simply reading in a file and want all your lines to be chomped, then IO::All with the chomp and tie options applied is very convenient.

Note also that the chomp trick doesn't work when slurping the entire contents of the handle into a scalar directly (that's just the way it is implemented).

jsegal