How to write shell scripts in Perl

First a really quick Perl tutorial.

What is Perl?

Perl is a great language for almost anything. The syntax is consice and Perl makes simple things simple and makes hard things possible or so the motto goes.
Perl is one of the best languages there is for manipulating text and also makes an excellent glue language because Perl allows you to choose your quote opperater, supports here documents, and has built in regular expressions.  Perl also has a bad reputation for looking like line noise for the same reasons listed above.

Perl is included by default on most *Nix distro's, Yes that includes you OSX users. Break open that terminal for once and play along.

Writting your first Perl script

create a new file that contains the following and save it as hello.pl

print "hello world";

Now from a terminal navigate to the dir containing the file and type:

perl hello.pl

You should see the output: hello world

If you want to be able to just run your perl script hello.pl without typing perl hello.pl you need to tell the system where the perl binary is in the first line of your perl script and make the file perl.pl executable.

type which perl from a terminal to determine the path to the perl binary. you should see something like this

regx@T60:~$ which perl
/usr/bin/perl
regx@T60:~$

So in this case I would make hello.pl look like this.

#! /usr/bin/perl

print "hello world";

If I make the file executable chmod ug+x hello.pl then I can run it by typing ./hello.pl or put it in my bin dir and just type hello.pl or remove the .pl and just type hello.

Without getting too off track we could write the same thing like this

#! /usr/bin/perl

print qq[hello world];
print qq!another quote example!;

qq means quote quote and allows you to choose quote characters. Brackets are special in that the closing bracket closes the quote. This makes writing other languages in perl really nice. For instance writing html or sql I can choose a quote opperator that is not in my string so I do not have to escape double or single quotes like you have to in most other languages. Ok, back to the main theme

Your first perl shell script.

Writing shell scripts in perl is really easy. You have several commands that allow you to run system commands the system command which wait for the command to finish and returns the output and exec which never returns. For shell scripts you usually want the use system. The is a nice syntax shortcut to the system command. Just put your command in backticks. Here is a simple perl shell script that list the contents of yourhome dir.

#! /usr/bin/perl

$dir = `ls ~/`;
print $dir;

Pretty simple ey?
Here we captured the output of the ls command to a string. Perl splits on newlines by default, so to capture as a array  is easy:

#! /usr/bin/perl

@dir = `ls ~/`;
# print the first item
print "the first item is $dir[0] \n";

#print the last item
print "that last item is $dir[-1] \n";

# traverse the array and print each item
for(@dir){
    print "$_ \n";
}

 "Lucy you got some splainin to do!"

Ok, here I introduced comments because it is so much easier to explain what is going on in the code via comments than to write it out and then have the example.
The @ means array, so by saying @dir = `ls ~/` we get the output from the ls command as an array bacause perl automatically splits on newlines by default.

Next we print the first item and use $dir[0] bacause arrays are zero indexed. We use the $ bacause we want a string back, not an array.
Then we print $dir[-1] which is a nice perl way to say the last item in the array.
Then we traverse the array with for which automagically populates $_. This works in nested loops as well and $_ is locally scoped to each loop.

What if we want the shell script to ask us which directory to list?

#! /usr/bin/perl

print "please enter a dir path\nPrint q to exit\n";
$path = <STDIN>;
chomp $path;
print `ls $path`;

<STDIN> grabs standard input, and chomp removes the trailing newline.
we could also say $path =~ s/\s*$//g;
If you know regular expresions. Perl has regular expresions built in better than any other language in my opinion, but that is a whole other topic.

OK, so now we know how to run system commands, how to get the output of system commands as strings or arrays and how to get input from the user.
You can write a ton of useful shellscripts with this information.

Time for one more really nice time saving trick. File IO in perl is a lot like other languages. You create a file handle and print or write to it and then close the file handle.
This is great and all, but *Nix systems power comes from the ability to chain lots of scripts together. How useful would grep be if it asked you what file or dir you want to grep. Not that usefull. How about ls? Could you imagine this

ls
what dir do you want to ls
~/
do you want to filter with grep
no

Perl has a really nice way of reading from <STDIN> and everything you print goes to <STDOUT> by default, so we are already half way there!
So lets say we want to be able to pass in any file and have are script count the words and vowels.

#! /usr/bin/perl

while(<>){
$lines +=1;
while(/[\w']{2,}|a|i/g){
$words ++;
}
while(/[aeiou]/g){
$vowels ++;
}
}
print "lines: $lines\nwords: $words\nvowels: $vowels\n";

Now to use this script we can just do cat filename | perl wordcount.pl or we can make it executable, rename it to wordcount and put it in out bin dir so we can just type
cat filename | wordcount

Notice in the above script I am looping through the file one line at a time and running the regular expressions to do the counting on each line to conserve memory.
We could easilly slurp in the whole file to a string like so:

while(<>){
  $text .= $_;
}

Or slurp the file in as an array like so

@text = <>;

We can write filters like this to do all kinds of cool stuff., and anything we print can be piped to a file, or to another nifty filter.
I use Quanta for most of the projects I work on. Quanta has a great feature where it can write to a log file any time a project is opened or closed.
I wanted to be able to comment the log file and print out task times as well as project totals.

Here is the simple script I use.

#! /usr/bin/perl

use Date::Manip;
#use strict;

my ($start,$stop,$total,$startsecs,$stopsecs,$jobdesc);
my @log = ();
print qq[
    Time Log - Created From Quanta Project Log

];

while(<>){
    my $logline = $_;
    my($day,$time,$event,$desc) = $logline =~ /([0-9\-]+?)T(\d\d:\d\d:\d\d): Event : (.+?) : Action: log :(.*)/ig;
    #print $logline;
    #print "$day $time [$event]\n";

    if($event eq 'after_project_open'){
        $start = "$day $time";
        $startsecs = UnixDate($start,"%s");
        $jobdesc = $desc;
            #print "start $startsecs\n";
    }
    elsif($event eq 'after_project_close'){
        $stop = "$day $time";
        $stopsecs = UnixDate($stop,"%s");
           #print "stop $stopsecs\n";
        if($startsecs){
            $diff = $stopsecs - $startsecs;
            $minutes = $diff / 60;
            $hours = sprintf("%.2f",$minutes / 60);
            $total = $total + $diff;
            $jobdesc .= " - $desc";

           
            print qq[$start  to  $stop [$hours hrs] - $jobdesc\n];
        }
    }
}
$total_min = sprintf("%.2f",$total / 60);
$total_hr = sprintf("%.2f",$total_min / 60);
print qq[
total project seconds: $total
total project min: $total_min
total project hours: $total_hr
];

The above script uses the awesome Date:Manip perl module.
You can install this module via cpan by typing cpan Date::Manip

For more information on Perl see http://perl.org

perl regular expressions: http://perldoc.perl.org/perlre.html
perl functions: http://perldoc.perl.org/perlfunc.html
perl variables: http://perldoc.perl.org/perlvar.html

To find perl modules see the wonderful
http://search.cpan.org