Sunday, September 12, 2010

Sort Date in "DD MMM YYYY" format

To sort date in "DD MMM YYYY" format (e.g. "02 DEC 2010")

#/usr/bin/perl -w
use strict;

# Constants
my %MONTH_NUMBER = (
    'JAN' => 1,
    'FEB' => 2,
    'MAR' => 3,
    'APR' => 4,
    'MAY' => 5,
    'JUN' => 6,
    'JUL' => 7,
    'AUG' => 8,
    'SEP' => 9,
    'OCT' => 10,
    'NOV' => 11,
    'DEC' => 12,    
);

my @myarray = (
    '08-DEC-10',
    '30-NOV-10',
    '26-NOV-10',
    '25-NOV-10',
    '25-NOV-10',
    '24-NOV-10',
    '11-NOV-10',
    '10-NOV-10',
    '10-NOV-10',
    '08-NOV-10',
    '03-NOV-10',
    '02-NOV-10',    
    '01-NOV-10',
);

my @sorted_array = sort sort_func @myarray;

print "@sorted_array"; 

####################
# Subroutine
####################
# Sort function
sub sort_func() {
    # To sort in desc, just swap $a and $b
    #return convert_datenumber($b) 
    #   cmp convert_datenumber($a);
  
    return convert_datenumber($a) 
       cmp convert_datenumber($b);
}

# Convert "DD MMM YYYY" into "YYYYMMDD"
sub convert_datenumber() {
    my $in_date = $_[0];
    
    my $out_date = 0;
            
    if (length($in_date)) {
        my $day        = substr($in_date, 0, 2);
        my $month      = $MONTH_NUMBER{ substr($in_date, 3, 3) };
        my $year       = substr($in_date, 7);
        
        $out_date = $year * 10000 
                  + $month * 100
                  + $day;                                    
    }
        
    return $out_date;
}
Output:

01-NOV-10 02-NOV-10 03-NOV-10 08-NOV-10 10-NOV-10 10-NOV-10 11-NOV-10 24-NOV-10 25-NOV-10 25-NOV-10 26-NOV-10 30-NOV-10 08-DEC-10

Monday, June 7, 2010

Arguments By Reference

Modify scalar, hash and list through a subroutine.
#!/usr/bin/perl -w
use strict;

my $name;
my %contacts;
my @children;

&my_function(\$name, \%contacts, \@children);


print "Name = $name\n";

foreach my $key ( keys(%contacts) ) {
    print "$key = $contacts{$key}\n";
}
 
foreach my $child (@children) {
    print "Child = $child\n";
}

sub my_function() {
    my $ref_name      = $_[0];
    my $ref_contacts  = $_[1];
    my $ref_children  = $_[2];
    
    $$ref_name = "Harry";
    $$ref_contacts{'home'} = '1234567890';
    $ref_contacts->{'work'} = '9876543210';

    push(@$ref_children, 'James');
    push(@$ref_children, 'Mary');

    print "Name = $$ref_name\n";
    
    foreach my $key ( keys(%$ref_contacts) ) {
        print "$key = $$ref_contacts{$key}\n";
    }
    
    foreach my $child (@$ref_children) {
        print "Child = $child\n";
    }
    print "\n";       
}

Saturday, June 5, 2010

Handle Command Line Arguments

Command line argument is commonly use in passing in the configuration to a Perl script. Fortunately, Perl's core module come with a parsing library i.e. GetOpt::Long which ease the parsing of arguments.

Here is the sample code:

#!/usr/bin/perl
use Getopt::Long;

my @files = ();
my $outputfile;

# Checking for missing arguments
if (@ARGV == 0 || 
  !GetOptions("input=s{1,}" => \@files, 
              "output|dest=s" => \$outputfile) || 
  @files == 0 || 
  !defined($outputfile)) { 
  # Print Usage
  print "Invalid arguments!\n"; 
  exit 1;
}
print "Total Input File: " . @files . "\n";
foreach my $file (@files) {
  print "-> $file\n";
}
print "Output File: $outputfile\n";

Output:


D:\>GetOptions.pl -i a.txt b.txt -d my.txt
Total Input File: 2
-> a.txt
-> b.txt
Output File: my.txt
The checking for the missing arguments:
  • Check for zero argument: if (@ARGV == 0 ||
  • Check options are in "s" or "o" or "d": !GetOptions("input=s{1,}" => \@files, "output|dest=s" => \$outputfile)
  • Check for no input files: @files == 0
  • Check for missing output file: !defined($outputfile)
To get multiple values the option, you can use "input=s{1,}", it will store the values into a list (@files).

NOTE: In Perl, a subroutine parameter start with back slash ("\") e.g. \@files, indicates pass by reference. It means the parameter can be changed through the subroutine.

Monday, May 31, 2010

How to Process Multiple Files from Wildcard Arguments

To process multiple files from wildcard arguments, use glob() function.
#!/usr/bin/perl
@files = glob("@ARGV");
foreach (@files) {
    print "File: $_\n";
}

Please notice that the @ARGV is surrounded by double quotes. Without double quotes, it will return the count of arguments.

Example:
D:\>process_files.pl test*.pl myperl.pl exec_cmd.pl
File: TestBack.pl
File: TestCSV.pl
File: TestDate.pl
File: myperl.pl
File: exec_cmd.pl

Saturday, May 29, 2010

Execute an External Command

In Perl, to execute an external command and capture its output can be done in one line.

#!/usr/bin/perl

# Use backticks (``) operator 
@output = `tasklist.exe`;

# Display the output
foreach $line (@output) {
    print "$line";
}
Output:

D:\>exec_cmd.pl

Image Name        PID Session Name  Session# Mem Usage
================= === ============= ======== ==========
System              4 Services             0   32,236 K
smss.exe          332 Services             0      760 K
csrss.exe         436 Services             0    1,728 K
wininit.exe       476 Services             0    2,020 K
csrss.exe         488 Console              1    8,268 K
You can use Perl to analysis the output and send an email alert if any error detected.

Friday, May 28, 2010

How to Debug a Perl Script

Programmers do not know how to debug is blind. Perl comes with a built-in debugger that allows you to do debugging without separately install a debugger.
To debug a Perl script, simply use "-d" flag:
Debug_Perl
Here are the common use commands:
  • s - Single step. It will step into a subroutine.
  • n - Next, steps over subroutines.
  • r - Return from the subroutine.
  • c - Continue until a breakpoint if any.
  • b [ln|event|sub] - Set a breakpoint.
  • l [line|sub] - List source code.
  • p expr - Print expression.
  • q - Quit.
For details, visit http://perldoc.perl.org/perldebug.html. Cheers!

Perl: Easy way to read a fixed length file

To parse a fixed length string, you can use unpack() function:
#Name(10), Age(2), Sex(6)
#Sample data:
# "John 20MALE  "
# "Mary 22FEMALE"
my $templateformat = "A10A2A5"; # Read unpack() documentations.
my @fields = unpack( $templateformat , "John 20MALE  ");
This is a simple example to read a fixed length file:
#!/usr/bin/perlopen(INFILE, $ARGV[0]);
my $templateformat = "A10A2A5";
while (<INFILE>) {
  my @fields = unpack( $templateformat , $_);
  print "$fields[0] is $fields[1] years old\n";
}
close(INFILE);
To run it:
C:\> readfixedlength.pl sampledata.txt
Output:
John is 20 years old
Mary is 22 years old
You can store the file format in a configuration file. It makes the script much easier to read and maintain. The script keeps the configuration after the __DATA__ token. NOTE: __DATA__ is a token that marks end of script. You can use DATA filehandle the text after it.
#!/usr/bin/perl
my @header_names = ();
my @header_lengths = ();
&load_template(\@header_names, \@header_lengths);
my $templateformat;
foreach (@header_lengths) { 
  $templateformat .= "A" . $_;
}
open(INFILE, $ARGV[0]);
while (<INFILE>) {
  my @fields = unpack( $templateformat , $_);
  print "$fields[0] is $fields[1] years old\n";
}
close(INFILE);
# Load from the template __DATA__ section
sub load_template() {
  # By-ref parameters
  my $ref_header_names = $_[0];
  my $ref_header_lengths = $_[1];
  # Load the file template from __DATA__  
  while (<DATA>) {
    chomp;
    # Skip comment or empty line.
    next if (/^#/ || /^$/);
    my @fields = split(",");
    push(@$ref_header_names, $fields[0]);
    push(@$ref_header_lengths, $fields[1]);
  }
}
__DATA__
FIELD_NAME,10
FIELD_AGE,2
FIELD_SEX,6
It is very simple, right?