Perl

10. Misc


Here Documents

You can do shell-like "here" documents in Perl.

Very useful for printing HTML in CGI.

print <<END;
blah
  blah
blah blee
END

print <<"END";           # text treated as double-quoted
hello $there
END

print <<'END';           # text treated as single-quoted
hello $there
END

print <<"" x 5;         # next line double-quoted, x 5
This is a line to be printed

output:

blah
  blah
blah blee
hello there
hello $there
This is a line to be printed
This is a line to be printed
This is a line to be printed
This is a line to be printed
This is a line to be printed

Advanced Sorting

Perl's builtin sort uses a quicksort algorithm. Used to rely on system's qsort() where possible, but recently went to own implementation due to bugs and inconsistencies among qsort()'s.

We can change the default sort comparison for the sort function by telling perl how to compare two items. We do this by declaring a subroutine that compares two things.

Subroutine you declare is to take two (global) values, $a and $b (not @_), and return a single value telling sort which one is greater or if they are equal. Don't recurse. Don't modify a or b.

#!/usr/bin/perl -w

# custom sort routine, compare two nums
sub by_num {
   if ($a < $b) {
      return(-1);
   }
   elsif ($a > $b) {
      return(1);
   }
   else {
      return(0);
   }
}

@values = (7, 2, 3, 9, 1);

print "values were: @values\n";

@sorted = sort by_num @values;

print " values are: @sorted\n";
Note the routine could have been written as:
sub by_num {
   return($a - $b);
}

Can even use "the spaceship operator" which does the three-way comparison for us:

sub by_num {
   $a <=> $b;            # return optional, automatically returns value of last expression
}
Even shorter, entire thing like this:
@sorted = sort { $a <=> $b } @values;

We also have a string-based spaceship-like operator, cmp for doing the same thing with strings.

Can sort hashes and arrays based on particular elements, break ties with other elements, etc.

#!/usr/local/bin/perl -w

@rec1 = ('Harvey', 'Smith', '567-8901');
@rec2 = ('Bill', 'Smith', '234-5678');
@rec3 = ('Harvey', 'Wallbanger', '345-6789');
@rec4 = ('Greg', 'Long', '123-4567');

@all = (\@rec1, \@rec2, \@rec3, \@rec4);   # refs, otherwise flattened

print "original list:\n";
foreach $rec (@all) {
   print "record: $$rec[1], $$rec[0]: $$rec[2]\n";
}

sub by_my_criteria {
   return($a->[1] cmp $b->[1] or $a->[2] cmp $b->[2]);
}

@new = sort by_my_criteria @all;
print "sorted by last, phone:\n";
foreach $rec (@new) {
   print "record: $$rec[1], $$rec[0]: $$rec[2]\n";
}

output:

original list:
record: Smith, Harvey: 567-8901
record: Smith, Bill: 234-5678
record: Wallbanger, Harvey: 345-6789
record: Long, Greg: 123-4567
sorted by last, phone:
record: Long, Greg: 123-4567
record: Smith, Bill: 234-5678
record: Smith, Harvey: 567-8901
record: Wallbanger, Harvey: 345-6789

Handling /etc/passwd

Easy to rip through /etc/passwd or the distributed passwd table using Perl to read lines and the split function to tear them up.

But Perl also gives some functions for easier access to some cases.

#!/usr/bin/perl -w

@passwd_entry = getpwnam("gslong");
print "gslong's entry: @passwd_entry\n";            # prints out 9 fields

@passwd_entry = getpwuid(100);
print "gslong's entry (by uid): @passwd_entry\n";   # also nine

$user = getpwuid(100);                              # who's got UID 100?
print "user with uid 100 is: $user\n";              # note get just name in
                                                    #  scalar context

$home = (getpwnam("gslong"))[7];                    # pick off one needed
print "gslong's home is: $home\n";

Note Perl returns nine fields for each user, handles cases of systems that include "quota" and breaking GCOS into two fields, "comment" and "gcos".

($user, $passwd, $uid, $gid, $quota, $comment, $gcos, $home, $shell) = getpwuid(100);

Can also use another set of functions for sequential, rather than random-access order...

setpwent() initializes, getpwent() returns next user's entry, endpwent() terminates.

From text:
setpwent();

while (@list = getpwent()) {
   ($login, $home) = @list[0,7];
   print "home = $home for user $login\n";
}

endpwent();

grep

Perl supports a grep function to operate on lists.

#!/usr/bin/perl -w

# slurp up entire passwd file
open(PASS, "/etc/passwd")     || die "can't read passwd\n";
chomp(@lines = <PASS>);
close(PASS);

# find lines with greg
@gregs = grep /^gslong:/, @lines;

# spit them out
foreach $greg (@gregs) {
   print "found greg: $greg\n";
}

# find lines without greg
@not_gregs = grep !/^gslong:/, @lines;

grep returns list of matches in list context, but the number of matches in scalar context.

grep plays with $_ as it searches, you can use this to modify the array.

@found = grep { s/^Greg/Long/g } @list;     # both found & list modified
                                            # found only has lines where repl occured

What happens to above if use $found instead of @found to assign result?


map

Kinda related to grep, will apply a function to all elements of a list through $_. Returns a frequently useless list containing as many elements as were found in original list, return values of the function applied to each element.

@a = ("greg", "long", "super", "cool");

map { s/^[a-z]/x/ } @a;          # list now: "xreg", "xong", etc

@a = ("greg", "long", "super", "cool");

map { $_ = ucfirst $_ } @a;      # list now: "Greg", "Long", etc
# the following modifies each element of @lines
# return list is list of counts of how many sucessful changes
#   were made on each line (undef in case of zero)
@counts = map { s/long/xxxx/g } @lines;


sub check_it {
   if ($_ =~ /g$/) {
      return("T");
   }
   else {
      return("F");
   }
}

@checked = map check_it, @lines;
              # @checked now contains T or F in each position
              # to note whether found g at end of element in @lines


Other Perl Functions

Refer to "perlfunc" man page for complete list.

Math functions: abs, cos, sin, log, etc.

Random: srand (seeds, with time by default), rand (generates)

$x = rand();          # 0.0 <= x < 1.0  ; floats
$x = rand(7);         # 0.0 <= x < 7.0  ; floats
$x = int(rand(10));   #   0 <= x < 10   ; integer values

Sockets: accept, bind, connect, listen, send, recv, getsockopt, setsockopt, etc.

Much more...