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;

#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 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 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.