CT320: Network and System Administration

Fall 2019

Perl

Show Lecture.Perl as a slide show.

CT320 Perl

Original slides from Dr. James Walden at Northern Kentucky University.

Language

Source: https://wikipedia.org/wiki/Perl

History

Source: https://wikipedia.org/wiki/Perl

Usage (continued)

Usage (continued)

Source: https://wikipedia.org/wiki/Common_Gateway_Interface

Documentation

Basic Perl

#! /usr/bin/perl -w
use 5.16.1;
say "Hello, world!"
Hello, world!
use 5.99.9;
Perl v5.99.9 required--this is only v5.26.3, stopped at .perl-code line 1.
BEGIN failed--compilation aborted at .perl-code line 1.

print vs. say

#! /usr/bin/perl -w
use 5.16.1;
print "a",'b',2+2;
say "d";
say "e";
say "f";
ab4d
e
f

Scalars

#! /usr/bin/perl -w
use 5.16.1;
my $alpha = 123456;
my $beta = 3.14159;
my $gamma = "String";
say '$alpha = ', $alpha;
say '$beta = ' . $beta;
say "\$gamma = $gamma";
$alpha = 123456
$beta = 3.14159
$gamma = String

Quotes

#! /usr/bin/perl -w
use 5.16.1;
my $answer = 42;
print "1: answer is ", $answer, "\n";
print "2: answer is $answer\n";
print '3: answer is $answer\n';
1: answer is 42
2: answer is 42
3: answer is $answer\n

Math

#! /usr/bin/perl -w
use 5.16.1;
say 1 + 2;
say 3 - 4;
say 5 * 6;
say 7 / 8;
say 3 ** 4;
say 13 % 2;
3
-1
30
0.875
81
1

Unlike some languages, int/int performs floating-point division.

Strings

#! /usr/bin/perl -w
use 5.16.1;
my $alpha = "Foo";
my $beta = 'Bar';
my $gamma= $alpha . $beta;
my $delta = 3;
my $epsilon = $beta x $delta;
say "alpha = $alpha";
say "beta = $beta";
say "gamma = $gamma";
say "delta = $delta";
say "epsilon = $epsilon";
alpha = Foo
beta = Bar
gamma = FooBar
delta = 3
epsilon = BarBarBar

Declarations

#! /usr/bin/perl -w
use 5.16.1;
my $alpha = 42;
print "alpha is $a1pha";
Global symbol "$a1pha" requires explicit package name (did you forget to declare "my $a1pha"?) at .perl-code line 4.
Execution of .perl-code aborted due to compilation errors.

The last line contained a digit one, as opposed to a lower-case L. Oops!

Sigils

#! /usr/bin/perl -w
use 5.16.1;
my $a = 5;          # a is a scalar
my @b = (11,22,33); # b is an array
$a = $b[1];         # b[1] is a scalar
say $a;
22

Perl ≠ Bash

Note the differences:

#! /bin/bash
let x=2+2
let x*=10
let y=$x+9
echo "x=$x y=$y"
x=40 y=49
#! /usr/bin/perl -w
use 5.16.3;
my $x=2+2;
$x*=10;
my $y=$x+9;
say "x=$x y=$y";
x=40 y=49

Arrays

#! /usr/bin/perl -w
use 5.16.1;
# Array Definition
my @stuff = ("apples", "pears", "eels");
my @numbers = (123, 456, 789, 987, 654, 321);
# Array access
say '$stuff[2] = ', $stuff[2];
say '$numbers[4] = ', $numbers[4];
$stuff[2] = eels
$numbers[4] = 654

Arrays (continued)

#! /usr/bin/perl -w
use 5.16.1;
# Array Definition
my @stuff = ("apples", "pears", "eels");
my @numbers = (123, 456, 789, 987, 654, 321);
# Adding and removing elements
push(@stuff, "eggs", "lard");
my @more = ("butter", "milk");
push(@stuff, @more);
print "stuff = @stuff\n";
my $grub = pop(@stuff);
print "grub = $grub\n";
print "stuff = @stuff\n";
stuff = apples pears eels eggs lard butter milk
grub = milk
stuff = apples pears eels eggs lard butter

Arrays (continued)

#! /usr/bin/perl -w
use 5.16.1;
# Length versus contents
my @a = (1,2,3,"cuatro","cinco","seis");
my $x = @a;
say "x = $x";
$x = "@a";
say "x = $x";
x = 6
x = 1 2 3 cuatro cinco seis

List manipulation

#! /usr/bin/perl -w
use 5.16.1;
my @a = (2,3,"cuatro","cinco");
push @a, "seis";
unshift @a, 1;
say "a: @a";
say "pop yields: ", pop(@a);
say "shift yields: ", shift(@a);
say "now, a: @a";
a: 1 2 3 cuatro cinco seis
pop yields: seis
shift yields: 1
now, a: 2 3 cuatro cinco

Hashes

Hashes are also called associative arrays. They’re like regular arrays, except that the subscripts, or indices, can be strings.

#! /usr/bin/perl -w
use 5.16.1;
my %phonebook = ("Jack" => 5551212, "Peter" => 8765329);
$phonebook{"George"} = 1234567;
say $phonebook{"Peter"};
say $phonebook{"George"};
if ($phonebook{"Jack"}) {
    say "Jack’s phone number is ", $phonebook{"Jack"};
}
if ($phonebook{"Mo"}) {
    say "Mo’s phone number is ", $phonebook{"Mo"};
}
8765329
1234567
Jack’s phone number is 5551212

if statements

#! /usr/bin/perl -w
use 5.16.1;
my $x = 42;
if ($x > 19) {
    say "Good!";
}
Good!

The () and {} are required.

While-loops are similar.

Postfix if

#! /usr/bin/perl -w
use 5.16.1;
my $x = 42;
say "Good!"
    if $x > 19;
Good!

Note the lack of parens and braces.

Reverse if

#! /usr/bin/perl -w
use 5.16.1;
my $x = 42;
say "Good!"
    unless $x == 99;
Good!

You can also use unless in a non-reversed if.

warn

warn is equivalent to print stderr.

warn "This message goes to standard error.\n";
This message goes to standard error.

die

if (3/2 > 1) {
    die "I didn’t expect that!";
}
I didn’t expect that! at .perl-code line 2.
open F, "</bogus/noway" or die;
Died at .perl-code line 1.

File I/O

#! /usr/bin/perl -w
use 5.16.1;
# File Input
open(NETS, "/etc/networks") or die "$0: can’t open /etc/networks";
while (<NETS>) {
    print $_;
}
close(NETS);
loopback 		127	loopback-net,localnet
colostate		129.82
cs.colostate		129.82.44

File I/O, take two

#! /usr/bin/perl -w
use 5.16.1;
# File Input
open(NETS, "/etc/networks") or die "$0: can’t open /etc/networks";
my @lines = <NETS>;
close(NETS);
print @lines;
loopback 		127	loopback-net,localnet
colostate		129.82
cs.colostate		129.82.44

File I/O, take three

#! /usr/bin/perl -w
use 5.16.1;
# File Input
open(NETS, "/etc/networks") or die "$0: can’t open /etc/networks";
print <NETS>;
close(NETS);
loopback 		127	loopback-net,localnet
colostate		129.82
cs.colostate		129.82.44

File I/O

    # File modes
    open(INFO, "datafile");   # Open for input
    open(INFO, ">datafile");  # Open for output
    open(INFO, ">>datafile"); # Open for append
    open(INFO, "<datafile");  # Open for input

File Output

    open(FILE, ">temp.txt");
    print FILE "This line goes to the file.\n";
    close(FILE);

Diamond Operator

#! /usr/bin/perl -w
# This program imitates the cat command.
use 5.16.1;
while (<>) {
    print;
}

Loops

#! /usr/bin/perl -w
use 5.16.1;
my $i = 0;
do {
    say "do while $i";
} while $i++ < 3;

my $j = 3;
while ($j-- > 0) {
    say "while $j";
}

for my $w (4..6) {
    say "for $w";
}

for (my $z=10; $z<20; $z+=3) {
    say "z=$z";
}
do while 0
do while 1
do while 2
do while 3
while 2
while 1
while 0
for 4
for 5
for 6
z=10
z=13
z=16
z=19

Poetry

Many Perl functions can be called without parentheses:

#! /usr/bin/perl -w
use 5.16.1;
print("How are you?\n");
my @a = (11,22,33);
push(@a, 42, sqrt(10));
say(pop(@a));
say("a: @a");
How are you?
3.16227766016838
a: 11 22 33 42
#! /usr/bin/perl -w
use 5.16.1;
print "How are you?\n";
my @a = (11,22,33);
push @a, 42, sqrt 10;
say pop @a;
say "a: @a";
How are you?
3.16227766016838
a: 11 22 33 42

Functions

#! /usr/bin/perl -w
use 5.16.1;

# Add all the numbers given as arguments.
sub total {
    my (@numbers) = @_;                 # Copy arguments
    my $sum = 0;
    foreach my $item (@numbers) {
        $sum += $item;
    }
    return $sum;
}

# Main program
my @data1 = (3,1,4,1,5,9);
my $answer = total(@data1);
say "Sum of @data1 is $answer";
say "Sum of (1..15) is ", total(1..15);
Sum of 3 1 4 1 5 9 is 23
Sum of (1..15) is 120

Factorial, take 1

#! /usr/bin/perl -w
use 5.16.1;

# Return the product of 1…the given argument.
sub factorial {
    my ($n) = @_;
    if ($n <= 1) {
        return $n;
    }
    else {
        return $n*factorial($n-1);
    }
}

say "5! = ", factorial(5);    # 5! = 5×4×3×2×1 = 120
say "69! = ", factorial(69);
5! = 120
69! = 1.71122452428141e+98

Factorial, take 2

#! /usr/bin/perl -w
use 5.16.1;

# Return the product of 1…the given argument.
sub factorial {
    my ($n) = @_;
    if ($n <= 1) {
        return $n;
    }
    return $n*factorial($n-1);
}

say "5! = ", factorial(5);    # 5! = 5×4×3×2×1 = 120
say "69! = ", factorial(69);
5! = 120
69! = 1.71122452428141e+98

Factorial, take 3

#! /usr/bin/perl -w
use 5.16.1;

# Return the product of 1…the given argument.
sub factorial {
    my ($n) = @_;
    return $n if $n <= 1;
    return $n*factorial($n-1);
}

say "5! = ", factorial(5);    # 5! = 5×4×3×2×1 = 120
say "69! = ", factorial(69);
5! = 120
69! = 1.71122452428141e+98

Factorial, take 4

#! /usr/bin/perl -w
use 5.16.1;

# Return the product of 1…the given argument.
sub factorial {
    my ($n) = @_;
    return $n <= 1 ? $n : $n*factorial($n-1);
}

say "5! = ", factorial(5);    # 5! = 5×4×3×2×1 = 120
say "69! = ", factorial(69);
5! = 120
69! = 1.71122452428141e+98

Factorial, take 5

#! /usr/bin/perl -w
use 5.16.1;

# Return the product of 1…the given argument.
sub factorial {
    my ($n) = @_;
    $n <= 1 ? $n : $n*factorial($n-1);
}

say "5! = ", factorial(5);    # 5! = 5×4×3×2×1 = 120
say "69! = ", factorial(69);
5! = 120
69! = 1.71122452428141e+98

External Programs

There are several ways to run external programs:

$out = `command`;
(Those are `backquotes`, not normal 'single quotes'.)
Execute the Linux command. The standard output of the command goes into $out.
$all = `command 2>&1`;
Executes the Linux command. The standard output and standard error of the command go into $all.
$status = system("command");
Execute the Linux command. The standard output and standard error are not captured at all, and just merge with the Perl program’s output. The exit code is captured in $status (zero for success, non-zero for failure).

External Command Examples

#! /usr/bin/perl -w
use 5.16.1;
my $a = `date`;     # Note backquotes
print $a;           # Includes a newline

my @info = `head -4 /etc/resolv.conf`;
print $info[0];     # first line
print $info[1];     # second line

my $status = system('cat /etc/hostname');
say 'The exit code was ', $status;
Sat Nov 23 05:38:05 MST 2024
search cs.colostate edu colostate.edu
nameserver 129.82.45.181
beethoven
The exit code was 0

Admin Example

$ cat /etc/resolv.conf
search cs.colostate edu colostate.edu
nameserver 129.82.45.181
nameserver 129.82.103.78
nameserver 129.82.103.79
#! /usr/bin/perl -w
use 5.16.1;
my $path = "/etc/resolv.conf";
open (CONFIG, $path);
say "Nameservers from $path:";
while (<CONFIG>) {
    chomp;
    my @z = split(' ', $_);
    say $z[1]
        if lc($z[0]) eq "nameserver";
}
Nameservers from /etc/resolv.conf:
129.82.45.181
129.82.103.78
129.82.103.79

Admin Example, take two

$ cat /etc/resolv.conf
search cs.colostate edu colostate.edu
nameserver 129.82.45.181
nameserver 129.82.103.78
nameserver 129.82.103.79
#! /usr/bin/perl -w
use 5.16.1;
my $path = "/etc/resolv.conf";
open (CONFIG, $path);
say "Nameservers from $path:";
while (<CONFIG>) {
    say $1
        if /^nameserver\s+(.+)$/i;
}
Nameservers from /etc/resolv.conf:
129.82.45.181
129.82.103.78
129.82.103.79

$_

$_ is the “default” variable. For many Perl actions, if you don’t give a varable, $_ steps up.

#! /usr/bin/perl -w
use 5.16.1;
for my $i (5..9) {
    say $i;
}
5
6
7
8
9
#! /usr/bin/perl -w
use 5.16.1;
for (5..9) {
    say $_;
}
5
6
7
8
9
#! /usr/bin/perl -w
use 5.16.1;
for (5..9) {
    say;
}
5
6
7
8
9
#! /usr/bin/perl -w
use 5.16.1;
say
    for 5..9;
5
6
7
8
9

undef

#! /usr/bin/perl -w
use 5.16.1;
my @a = ("alpha", 'beta', undef, "delta");
for my $i (0..5) {
    if (defined($a[$i])) {
        say "a[$i]: $a[$i]";
    }
    else {
        say "There is no a[$i]";
    }
}
a[0]: alpha
a[1]: beta
There is no a[2]
a[3]: delta
There is no a[4]
There is no a[5]

Arguments

#! /usr/bin/perl -w
use 5.16.1;
say "This program is $0.";

# Display all arguments:
for (my $i=0; $i<@ARGV; $i++) {
    say "arg $i: $ARGV[$i]";
}
This program is .perl-code.

Context

Consider this C program. Why doesn’t it display one-third?

#include <stdio.h>
int main() {
    double d = 1/3;
    printf("%f\n", d);
    return 0;
}
0.000000

Context matters

In Perl, however, context matters:

#! /usr/bin/perl -w
use 5.16.1;
my @alpha = ("Every", "good", "boy", "does", "fine");
my @beta = @alpha;
say "beta is @beta";
my $gamma = @alpha;
say "gamma is $gamma";
beta is Every good boy does fine
gamma is 5

Pattern matching

#! /usr/bin/perl -w
use 5.16.1;
my $a = "Constantinople";
say "$a ends with an 'e'"
    if $a =~ /e$/;
say "$a starts with a digit"
    if $a =~ /^\d/;
Constantinople ends with an 'e'

More pattern matching

#! /usr/bin/perl -w
use 5.16.1;
my @animals = ("dog", "fish", "bear", "snake", "beaver");
for (1..9) {
    my $a = $animals[rand(@animals)];
    if ($a =~ /[aeiouy].*[aeiouy]/) {
        say "$a has at least two vowels.";
    }
    else {
        say "$a has less than two vowels.";
    }
}
beaver has at least two vowels.
bear has at least two vowels.
bear has at least two vowels.
snake has at least two vowels.
dog has less than two vowels.
bear has at least two vowels.
snake has at least two vowels.
snake has at least two vowels.
dog has less than two vowels.

Implicit pattern matching

If the =~ operator is not present, then the pattern matches against the special variable $_.

#! /usr/bin/perl -w
use 5.16.1;
open INFO, "/proc/cpuinfo" or die;
my $num_procs=0;
while (<INFO>) {
    $num_procs++ if /^processor/;
}
say "This computer has $num_procs processors.";
This computer has 12 processors.

Better implicit pattern matching

Rather than iterate explicitly, let’s use grep:

#! /usr/bin/perl -w
use 5.16.1;
open INFO, "/proc/cpuinfo" or die;
my $num_procs = grep {/^processor/} <INFO>;
say "This computer has $num_procs processors.";
This computer has 12 processors.

grep

grep takes two arguments:

my @greek = ('alpha', 'beta', 'gamma', 'delta');
my @e = grep {/e/} @greek;
print "@e";
beta delta

It calls the predicate code once per list item, with $_ as the item, returning either:

How is it determined which is returned?

More Implicit pattern matching

#! /usr/bin/perl -w
use 5.16.1;
open M, "/proc/mounts" or die;
my @filesystems;
while (<M>) {
    push @filesystems, "$1 on $2\n"
        if /^(\/dev\/\S+) (\S+)/;
}
print sort(@filesystems);
/dev/md1 on /s/beethoven/b
/dev/sda1 on /boot/efi
/dev/sda3 on /
/dev/sda3 on /var/tmp
/dev/sda4 on /s/beethoven/a

Some regexp syntax

Changing text: problem statement

Changing text, pass 1

#! /usr/bin/perl -w
use 5.16.1;
open H, "/etc/hostname" or die;     # Quite casual error message!
my $name = <H>;
print "Original hostname: $name";
for (my $i=0; $i<length($name); $i++) {
    my $l = substr($name, $i, 1);
    if ($l eq 'a' || $l eq 'e' || $l eq 'i' || $l eq 'o' || $l eq 'u') {
        substr($name, $i, 1) = '*';
    }
}
print "New hostname: $name";
Original hostname: beethoven
New hostname: b**th*v*n

This is how a C programmer would do it.

Changing text, pass 2

#! /usr/bin/perl -w
use 5.16.1;
open H, "/etc/hostname" or die;
my $name = <H>;
print "Original hostname: $name";
for (my $i=0; $i<length($name); $i++) {
    my $l = substr($name, $i, 1);
    if ($l =~ /[aeiou]/) {
        substr($name, $i, 1) = '*';
    }
}
print "New hostname: $name";
Original hostname: beethoven
New hostname: b**th*v*n

Better …

Changing text, pass 3

#! /usr/bin/perl -w
use 5.16.1;
open H, "/etc/hostname" or die;
my $name = <H>;
print "Original hostname: $name";
$name =~ s/[aeiou]/*/g;             # sed syntax  
print "New hostname: $name";
Original hostname: beethoven
New hostname: b**th*v*n

This is how a Perl programmer would do it.

Why didn’t we need to use say or add a \n?

Packages

Source: https://www.cpan.org/modules/

Features

Source: https://linuxconfig.org/perl-programming-tutorial/