The GECOS Phonebook in Perl – Now with bugfixes

Screenies of my GECOS Phonebook App in action

Screenies of my GECOS Phonebook App in action

I wrote this over a period of 3 days in Perl.  It’s 252 lines of Perl code and it works great!  The “Office Phone” of 414-744-4609 is the number for the Lake Masonic Center, BTW.

Here are t3h codes:

#!/usr/bin/perl
use strict;
use warnings;

# phonebook.pl
# A script to build a phonebook from the data in the
# /etc/passwd GECOS field
#

# CHANGELOG
# 073:20100531:01 change conditional to a one-liner
# 091:20100531:02 change conditional to one-liner
# 184:20100531:03 change conditional to a one-liner
# 044:20100531:04 perl-ify the error handling
# 056:20100531:05 perl-ify the error handling
# 065:20100531:06 perl-ify the error handling
# 081:20100531:07 perl-ify the error handling
# 112:20100531:08 perl-ify the error handling
# 128:20100531:09 perl-ify the error handling
# 203:20100531:10 reverse direction of conditional
# 215:20100531:11 fix a whitespace issue with split(/ / ) that perltidy messed with

# OPTIONS
my $GROUPARG = "-g";

# Variables
my $groupname = "";
my $arg;
my $groupent;
my @groupdata;
my $groupmembers;
my @memberlist;
my @mpwent;

#my $isGID     = 0;
my $testfield  = 0;
my $groupfound = 0;

my @gecos;
my @phonebook;
my @phonebook_keys = (
    'Last Name',
    'First Name',
    'Unix Name',
    'Office',
    'Office Phone',
    'Home Phone',
    'E-mail address'
);
my %phonebookent =
  map { $_ => "" } @phonebook_keys;    # thanks to raldi@stackoverflow
my %columnwidths = map { $_ => length($_) + 4 } @phonebook_keys;

print "\n";

# Check for group on command line
foreach ( 0 .. $#ARGV ) {
    $arg = $ARGV[$_];
    if ( $arg eq $GROUPARG ) {

        # chek for next argument
        # 20100531:04 perl-ify the error handling
        die("$arg needs a group name")
          if ( $_ == $#ARGV );    # if we're at the last argument

        #		if ($_ == $#ARGV) { # if we're at the last argument
        #			# fail
        #			print STDERR "$arg needs a group name\n";
        #			exit 1;
        #		}

        $groupname = $ARGV[ ++$_ ];

        # 20100531:05 perl-ify the error handling
        die("$arg needs a group name")
          if ( length($groupname) < 1 );    # if we have an empty string

        #		if (length($groupname) < 1) { # if we have an empty string
        #			# fail
        #			print STDERR "$arg needs a group name\n";
        #			exit 1;
        #		}

        # 20100531:06 perl-ify the error handling
        die("$arg needs a group name")
          if ( substr( $groupname, 0, 1 ) eq '-' )
          ;    # if the next argument starts with a -
         #if (substr($groupname, 0, 1) eq '-') { # if the next argument starts with a -
         #	# fail
         #	print STDERR "$arg needs a group name\n";
         #	exit 1;
         #}

        #DEBUG print group
        #print "Group $groupname\n";
    }
}

# check if we got a group
if ( length($groupname) > 0 ) {

    # fetch the members of that group
    # 20100531:07 perl-ify the error handling
    open( GROUP_FH, "/etc/group" )
      or die("Couldn't open /etc/group");    # try to open /etc/group
        #if (!open(GROUP_FH, "/etc/group")) { # try to open /etc/group
        #	# fail if open() fails
        #	print STDERR "Couldn't open /etc/group\n";
        #	exit 1;
        #}

    # check if we have a GID or group name
    # 20100531:01 change conditional to a one-liner
    # regex for digit test from Graham Ellis of wellho.net
    $testfield = 2
      if ( $groupname =~ /^-?\d/ )
      ;    # regex for digit test from Graham Ellis of wellho.net

    # read in /etc/group, line by line, checking for the name or GID we want
    while ( $groupent = <GROUP_FH> ) {
        @groupdata = split( /:/, $groupent );

        if ( $groupdata[$testfield] eq $groupname ) {

            # we found it - read the members
            $groupfound   = 1;
            $groupmembers = $groupdata[3];
            chomp $groupmembers;

            # if we actually have members
            # 20100531:02 change conditional to one-liner
            @memberlist = split( /,/, $groupmembers )
              if ( length($groupmembers) > 0 );
        }

    }

    # 20100531:08 perl-ify the error handling
    die("Group $groupname not found in /etc/group")
      unless ($groupfound);    # if we didn't find the requested group
         #if (!$groupfound) { # if we didn't find the requested group
         #	# fail
         #	print STDERR "Group $groupname not found in /etc/group\n";
         #	exit 1;
         #}

}

else {

    # just fetch all entries in /etc/passwd
    my @pwdata;
    my $pwent;

    # 20100531:09 perl-ify the error handling
    open( PASSWD_FH, '/etc/passwd' ) or die("Unable to open /etc/passwd");

    #if (!open(PASSWD_FH, '/etc/passwd')) {
    #	# fail
    #	print STDERR "Unable to open /etc/passwd\n";
    #	exit 1;
    #}

    while ( $pwent = <PASSWD_FH> ) {
        @pwdata = split( /:/, $pwent );
        push( @memberlist, $pwdata[0] ); # push the username onto the memberlist
    }

    close PASSWD_FH;
}

# walk through the members list
my $member;

foreach $member (@memberlist) {

    #print $member . "\t"; #DEBUG

    my $name;
    my $passwd;
    my $uid;
    my $gid;
    my $quota;
    my $comment;
    my $gcos;
    my $dir;
    my $shell;
    my $expire;
    (
        $name,    $passwd, $uid, $gid,   $quota,
        $comment, $gcos,   $dir, $shell, $expire
    ) = getpwnam($member);

    #print $name   . "\t"; #DEBUG
    #print length($member) . "\t"; #DEBUG
    #my @pwline = getpwnam($member); #DEBUG
    #print $pwline[0] .  "\n"; #DEBUG

# check if we actually got a pwent structure by checking the UID
#if (!defined($uid)) {
# fail
#	print STDERR "/etc/passwd out of sync with /etc/group - group $groupname member $member not found in /etc/passwd\n";
#	next;
#}

    # build the phonebook table entry
    my %phonebookent =
      map { $_ => "" } @phonebook_keys;    # thanks to raldi@stackoverflow
    @phonebookent{ 'Unix Name', 'E-mail address' } =
      ( $name, "$name\@lakemasoniccenter.org" );

    if ( length($gcos) > 0 ) {
        @gecos = split( /,/, $gcos );
	# 20100531:11 fix a whitespace issue with split(/ / ) that perltidy messed with
        @phonebookent{ 'First Name', 'Last Name' } = split(/ /, $gecos[0] );
        @phonebookent{ 'Office', 'Office Phone', 'Home Phone' } =
          ( $gecos[1], $gecos[2], $gecos[3] );
    }

    else {
        @phonebookent{ 'First Name', 'Last Name' } = ( "", "" );
        @phonebookent{ 'Office', 'Office Phone', 'Home Phone' } =
          ( "", "", "" );
    }

    # housekeeping
    foreach my $key (@phonebook_keys) {

        # 20100531:03 change conditional to a one-liner
        # 20100531:10 reverse direction of conditional
        $phonebookent{$key} = "" unless ( defined( $phonebookent{$key} ) );

        my $columnwidth = length( $phonebookent{$key} ) + 4;

        #DEBUG
        #print $key . "\t" . $columnwidth;

        if ( $columnwidth > $columnwidths{$key} ) {
            $columnwidths{$key} = $columnwidth;

            #print "\nNew Column Width for $key is $columnwidth.\n"; #DEBUG
        }

    }

    # add the entry to the phonebook
    my $phonebookent_ref = \%phonebookent;

    #print "pushing $name\n"; #DEBUG
    push( @phonebook, $phonebookent_ref );
}

# sort the phone book by name (thanks to cbkihong@unix.com, http://www.unix.com/members/20503.html)
@phonebook = sort {
        $$a{'Last Name'}
      . $$a{'First Name'} cmp $$b{'Last Name'}
      . $$b{'First Name'}
} @phonebook;

# print header
my $maxwidth   = 0;
my $numentries = scalar(@phonebook);

#print $numentries; #DEBUG
my $key        = "";
my $entrywidth = 0;
my $padding    = 0;

foreach (@phonebook_keys) {
    $maxwidth += $columnwidths{$_
      };         # add the maximum width of the column to the overall screen max
    print $_;    # print the column header
    $entrywidth = length($_);    # get the actual width of the entry
    $padding =
      $columnwidths{$_} -
      $entrywidth - 2;    # calculate number of spaces needed to pad column

    for ( my $i = 0 ; $i < $padding ; $i++ ) {
        print " ";
    }

}

print "\n";

for ( my $i = 0 ; $i < $maxwidth ; $i++ ) {
    print '-';
}

print "\n";

for ( my $i = 0 ; $i < $numentries ; $i++ ) {
    foreach $key (@phonebook_keys) {
        print $phonebook[$i]{$key};
        $entrywidth = length( $phonebook[$i]{$key} ) + 2;
        $padding    = $columnwidths{$key} - $entrywidth;

        for ( my $j = 0 ; $j < $padding ; $j++ ) {
            print " ";
        }

    }

    print "\n";
}

print "\n";
exit 0;
About these ads
  1. #1 by Joshua on May 28, 2010 - 1:32 PM

    The script is available here.

    If you wanna do a copy/paste from this blog, you’ll have to change the \ into line continuations manually.

  2. #2 by Joshua on May 28, 2010 - 1:36 PM

    I also forgot to blur the name out of the very top line. I hope Bro. Gary Parker doesn’t mind that everyone knows that he’s the Recorder of Ivanhoe Commandery.

  3. #3 by Joshua on May 31, 2010 - 8:47 AM

    The changes are mostly code-cosmetic. I’m a C programmer first and a Perl programmer second so I tend to do everything the C way. Perl is flexible enough that that works well but I’d rather use Perl the way it was intended and take advantage of all the neat syntactic tricks it enables.

  1. Web-izing command line apps with PHP « Around Teh Table

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 48 other followers

%d bloggers like this: