Appendix A

Answers


CONTENTS

Answers for Day 1, "Getting Started"

Quiz

  1. Perl has the power of a high-level programming language such as C, and the ease of use of simple languages such as shell scripts.
  2. The Perl interpreter executes your Perl program (starting from the beginning, and continuing one statement at a time).
  3. The answers are as follows:
    1. A statement is one particular task or instruction (usually corresponding to a line of code). A statement is terminated by a semicolon (;).
    2. A token is the smallest unit of information understood by the Perl interpreter. A statement consists of several tokens.
    3. An argument is an item passed to a library function (such as $inputline to print).
    4. Error recovery occurs when the Perl interpreter detects an error in your program. The interpreter tries to deduce what you meant to write and attempts to continue detecting errors in the program.
    5. The standard input file is the file that stores the characters you enter at the keyboard.
  4. A comment is any text that is preceded by a #. A comment can appear anywhere in your program. Everything after the # character is assumed to be part of the comment.
  5. Perl usually is located in the file /usr/local/bin/perl.
  6. The header comment is the special comment that tells the system that this is a Perl program. It appears as the first line of every Perl program.
  7. A library function is defined as part of the Perl interpreter and performs a specific task.

Exercises

  1. The following is one possible solution:
    #!/usr/local/bin/perl
    $inputline = <STDIN>;
    print ($inputline, $inputline);
  2. The following is one possible solution:
    #!/usr/local/bin/perl
    $inputline = <STDIN>;
    print ($inputline);
    $inputline = <STDIN>;
    print ($inputline);
  3. The following is one possible solution:
    #!/usr/local/bin/perl
    $inputline = <STDIN>;
    $inputline = <STDIN>; # this throws away the previous input line
    print( $inputline );
  4. The third line of the program is missing a semicolon at the end of the statement:
    #!/usr/local/bin/perl
    $inputline = <STDIN>;
    print ($inputline);
  5. The print($inputline) line is ignored because the entire third line is being treated as a comment. You want the following instead:
    #!/usr/local/bin/perl
    $inputline = <STDIN>;
    print($inputline); # print my line!
  6. This program reads two lines of input and prints them in reverse order (second line first).

Answers for Day 2, "Basic Operators and Control Flow"

Quiz

  1. The answers are as follows:
    1. An expression is a collection of operators and the values on which they operate.
    2. An operand is a value associated with an operator.
    3. A conditional statement is a statement that is executed only when its conditional expression is true.
    4. A statement block is a collection of statements contained inside the braces of a conditional statement. The statement block is executed only when the conditional expression associated with its conditional statement is true.
    5. An infinite loop is a conditional statement whose conditional expression is always true.
  2. A while statement stops looping when its conditional expression is false.
  3. An until statement stops looping when its conditional expression is true.
  4. The == operator compares its two operands. If they are numerically equal, the == operator yields a result of true; otherwise, it yields false.
  5. 27.
  6. The legal ones are a, c, and f.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl

    print ("Enter a number to be multiplied by 2:\n");
    $number = <STDIN>;
    chop ($number);
    $number = $number * 2;
    print ("The result is ", $number, "\n");
  2. Here is one possible solution:
    #!/usr/local/bin/perl

    print ("Enter the dividend (number to divide):\n");
    $dividend = <STDIN>;
    chop ($dividend);
    print ("Enter the divisor (number to divide by):\n");
    $divisor = <STDIN>;
    chop ($divisor);
    if ($divisor == 0) {
    print ("Error: can't divide by zero!\n");
    } elsif ($dividend == 0) {
    $result = $dividend;
    } elsif ($divisor == 1) {
    $result = $dividend;
    } else {
    $result = $divisor / $dividend;
    }
    if ($divisor == 0) {
    # skip the print, since we detected an error
    } else {
    print ("The result is ", $result, "\n");
    }
  3. Here is one possible solution:
    #!/usr/local/bin/perl

    $count = 1;
    $done = 0;
    while ($done == 0) {
    print ($count, "\n");
    if ($count == 10) {
    $done = 1;
    }
    $count = $count + 1;
    }
  4. Here is one possible solution:
    #!/usr/local/bin/perl

    $count = 10;
    until ($count == 0) {
    print ($count, "\n");
    $count = $count - 1;
    }
  5. There are, in fact, three separate bugs in this program:
    1. You must call chop to get rid of the trailing newline character in $value before comparing it to 17.
    2. The conditional expression should read $value == 17, not $value = 17.
    3. There should be a closing brace } before the else.
  6. This program contains an infinite loop. To fix it, add the following statement just before the closing brace }:
    $input = $input + 1;
    Also, the statement
    $input = $terminate + 5;
    should read
    $terminate = $input + 5;

Answers for Day 3, "Understanding Scalar Values"

Quiz

  1. The answers are as follows:
    1. A round-off error is the difference between the floating-point number that appears in a program and the number as it is represented in the machine.
    2. Octal notation is another way of referring to base-8 notation: Each digit can be a number from 0 to 7 and is multiplied by 8 to the exponent n, where n is the number of digits to skip.
    3. The precision of a floating-point representation on a machine is the number of significant digits it can hold.
    4. Scientific notation is a way of writing floating-point numbers. It consists of one digit before the decimal point, as many digits as required after the decimal point, and an exponent.
  2. The answers are as follows:
    1. 255 (the ASCII end-of-file character)
    2. 6
    3. 601
  3. The answers are as follows:
    1. 255
    2. 17
    3. 48813
  4. This line prints I am bored, and then backspaces over bored and replaces it with happy!. (I don't know a lot of practical uses for the \b escape character, but it's fun to watch.)
  5. The answers are as follows:
    1. This string contains 21.
    2. \21 is my favorite number.
    3. Assign \$num to this string.
  6. The answers are as follows:
    1. 4.371e01
    2. 6.0e-08 (the .0 is optional)
    3. 3.0e+00 (actually, 3 by itself is acceptable)
    4. -1.04e+00

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl

    $count = 1;
    $number = 0.1;
    until ($count == 10) {
    print ("$number\n");
    $number = $number + 0.1;
    $count = $count + 1;
    }
  2. Here is one possible solution:
    #!/usr/local/bin/perl

    $inputline = <STDIN>;
    chop ($inputline);
    if ($inputline == 0) {
    print ("0\n");
    } else {
    print ("1\n");
    }
  3. Here is one possible solution:
    #!/usr/local/bin/perl

    print ("Enter a number:\n");
    $number = <STDIN>;
    chop ($number);
    until ($number == 47) {
    print ("Wrong! Try again!\n");
    $number = <STDIN>;
    chop ($number);
    }
    print ("\aCorrect!\n");
  4. The first string in the print statement is not terminated properly, because there is a backslash \ before the final '. To fix this, add another quote:
    print ('here is the value of \$inputline\'', ": $inputline");
  5. This code fragment does not produce the expected result because of a round-off error. Try subtracting $num3 from $num1 before adding $num2 and $num4.
  6. "0xce" converts to 0, not to the hexadecimal constant 0xce. To fix this, leave off the quotes.

Answers for Day 4, "More Operators"

Quiz

  1. The answers are as follows:
    1. An operator is a character or string of characters that represents a particular Perl operation.
    2. An operand is a value used by an operator. In Perl, operators require one, two, or three operands.
    3. An expression is a collection of operators and operands, yielding a final result.
    4. Operator precedence is the order in which different types of operations are performed.
    5. Operator associativity is the order in which operations of the same precedence are performed.
  2. The answers are as follows:
    1. logical AND
    2. bitwise AND
    3. bitwise XOR
    4. string inequality
    5. string concatenation
  3. The answers are as follows:
    1. eq
    2. %
    3. x
    4. |
    5. >=
  4. The answers are as follows:
    1. 0000000010101011
    2. 0000010001010001
    3. 0 (or 00000000)
  5. The answers are as follows:
    1. 100
    2. 15
    3. 65
  6. The answers are as follows:
    1. 4
    2. 0 (I hope you didn't calculate all of the expression! Once you see the first 0, you should know that the result is 0.)
    3. 1819
    4. "abcdede"

Exercises

  1. The following is just one of many possible answers:
    #!/usr/local/bin/perl

    $value = 1;
    $counter = 0;
    while ($counter < 16) {
    print ("2 to the power $counter is $value\n");
    $value = $value << 1;
    $counter++;
    }
  2. The answer is as follows:
    $result = $var1 == 5 || $var2 == 7 ?
    $var1 * $var2 + 16.5 :
    (print("condition is false\n"), 0);
  3. The answer is as follows:
    if ($var1 <= 26) {
    $result = ++$var2;
    } else {
    $result = 0;
    }
  4. The following is just one of many possible answers:
    #!/usr/local/bin/perl

    print("Enter the integer to be divided:\n");
    $dividend = <STDIN>;
    print("Enter the integer to divide by:\n");
    $divisor = <STDIN>;
    # check for division by zero
    if ($divisor == 0) {
    print("error: can't divide by zero\n");
    } else {
    $quotient = $dividend / $divisor;
    $remainder = $dividend % $divisor;
    print("The result is $quotient\n");
    print("The remainder is $remainder\n");
    }
  5. Adding 100005.2 and then subtracting it causes round-off errors, which means that the final value isn't exactly the same as 5.1.
  6. ($result = ((($var1 * 2) << (5 + 3)) || ($var2 ** 3))), $var3;
  7. 81
  8. Here is the corrected program, with the fixed errors listed:
    #!/usr/local/bin/perl

    $num = <STDIN>;
    chop ($num);
    $x = "";
    $x .= "hello"; # += is for integers
    if ($x ne "goodbye" || $x eq "farewell") {
    # the previous line had two problems:
    # the operators were numeric, not string;
    # the or operator was bitwise, not logical.
    $result = $num == 0 ? 43 : 0;
    # the : and third operand were missing in the previous
    # line; eq replaced by ==
    } else {
    $result = ++$num; # can't have ++ on both sides
    }
    print("the result is $result\n");

Answers for Day 5, "Lists and Array Variables"

Quiz

  1. The answers are as follows:
    1. A list is an ordered collection of scalar values.
    2. An empty list is a list with zero elements in it.
    3. An array variable is a variable that can store a list.
    4. A subscript is a scalar value that refers to an element of a list. The subscript 0 refers to the first element, the subscript 1 refers to the second, and so on.
    5. An array slice is a list consisting of some elements of an array variable. (Notice that the elements do not have to be in order.)
  2. The answers are as follows:
    1. (1, 2, 3)
    2. (3, 2)
    3. ("hello", 2, 2)
    4. (2, 3)
    5. ("", 3, 2, 2)
    6. The contents of the standard input file, one line per list element.
  3. The answers are as follows:
    1. 2
    2. 4
    3. "one"
    4. 2
    5. "three"
    6. "" (Only three elements in the list are stored in @list2.)
  4. A list is a collection of scalar values. An array variable is a place where you can store a list.
  5. The brackets [] enclosing the subscript distinguish an array element from a scalar variable.
  6. You can do this in many ways. The two easiest are
  7. You can obtain the length of a list stored in an array variable by assigning the array variable to a scalar variable.
  8. All undefined array elements are assumed to contain the null string "".
  9. When you assign to an array element that is larger than the current length of the array, the array grows to include the new element.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl

    $thecount = 0;
    $line = <STDIN>;
    while ($line ne "") {
    chop ($line);
    @words = split(/ /, $line);
    $wordindex = 1;
    while ($wordindex <= @words) {
    if ($words[$wordindex-1] eq "the") {
    $thecount += 1;
    }
    $wordindex++;
    }
    $line = <STDIN>;
    }
    print ("Total occurrences of \"the\": $thecount\n");
  2. Here is one possible solution:
    #!/usr/local/bin/perl

    $grandtotal = 0;
    $line = <STDIN>;
    while ($line ne "") {
    $linetotal = 0;
    @numbers = split(/ /, $line);
    $numbercount = 1;
    while ($numbercount <= @numbers) {
    $linetotal += $numbers[$numbercount-1];
    $numbercount++;
    }
    print("line total: $linetotal\n");
    $grandtotal += $linetotal;
    $line = <STDIN>;
    }
    print("grand total: $grandtotal\n");
  3. Here is one possible solution:
    #!/usr/local/bin/perl

    @lines = <STDIN>;
    chop (@lines);
    $longlongline = join(" ", @lines);
    @words = split(/ /, $longlongline);
    @words = reverse sort (@words);
    $index = 0;
    print("Words sorted in reverse order:\n");
    while ($index < @words) {
    # note that the first time through, the following
    # comparison references $words[-1]. This is all
    # right, as $words[-1] is replaced by the null
    # string, and we want the first word to be printed
    if ($words[$index] ne $words[$index-1]) {
    print ("$words[$index]\n");
    }
    $index++;
    }
  4. The array element reference should be $array[4], not @array[4].
  5. There are four separate bugs in this program:
    1. You must call chop to remove the newline characters from the input lines stored in @input. Otherwise, they make your output unreadable.
    2. Similarly, you have to append a newline when calling join:
      $input[$currline] = join(" ", @words, "\n");
    3. The conditional expression should read
      $currline <= @input
      instead of
      $currline < @input
      to make sure that the last line of the input file is read.
    4. Your subscripts should read [$currline-1], not [$currline]. (This bug will keep coming up in your programs because it's easy to forget that subscripts start with zero.)

Answers for Day 6, "Reading from and Writing to Files"

Quiz

  1. The answers are as follows:
    1. A file variable is a name that represents an open file.
    2. A reserved word is a word that can't be used as a variable name because it has a special meaning in Perl (such as if).
    3. The file mode specifies how you want to access a file when you open it (read, write, or append).
    4. Append mode indicates that you want to open the file for writing and append anything you write to the existing contents of the file.
    5. A pipe is a connection between output from one program and input to another.
  2. The <> operator reads its data from the files specified on the command line.
  3. The answers are as follows:
    1. -r tests whether you have permission to read a file.
    2. -x tests whether you have permission to execute a file (and whether the file is executable).
    3. -s indicates the size of a file in bytes.
  4. @ARGV contains the following list:
    ("file1", "file2", "file3")
  5. The answers are as follows:
    1. To open a file in write mode, put a > character in front of the filename.
    2. To open a file in append mode, put two > characters (>>) in front of the filename.
    3. To open a file in read mode, just specify the filename. By default, files are opened in read mode.
    4. To open a pipe, put a | character in front of the command to be piped to.
  6. The <> operator reads data from the files whose names are stored in the array variable @ARGV. When the <> operator runs out of data in one file, it opens the file named in $ARGV[0] and then calls shift to move the elements of @ARGV over.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl

    $total = 0;
    $count = 1;
    while ($count <= @ARGV) {
    $total += $ARGV[$count-1];
    $count++;
    }
    print ("The total is $total.\n");
  2. Here is one possible solution:
    #!/usr/local/bin/perl

    $count = 1;
    while ($count <= @ARGV) {
    if (-e $ARGV[$count-1] && -s $ARGV[$count-1] > 10000) {
    print ("File $ARGV[$count-1] is a big file!\n");
    }
    $count++;
    }
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    open (INFILE, "file1") ||
    die ("Can't open file1 for reading\n");
    open (OUTFILE, ">file2") ||
    die ("Can't open file2 for writing\n");
    # the following only works if file1 isn't too big
    @contents = <INFILE>;
    print OUTFILE (@contents);
    # we don't really need the call to close, but they
    # make things a little clearer
    close (OUTFILE);
    open (OUTFILE, ">>file2") ||
    die ("Can't append to file2\n");
    print OUTFILE (@contents);
  4. Here is one possible solution:
    #!/usr/local/bin/perl

    $wordcount = 0;
    while ($line = <>) {
    # this isn't the best possible pattern to split with,
    # but it'll do until you've finished Day 7
    @words = split(/ /, $line);
    $wordcount += @words;
    }
    open (MESSAGE, "| mail dave") ||
    die ("Can't mail to userid dave.\n");
    print MESSAGE ("Total number of words: $wordcount\n");
    close (MESSAGE);
  5. Here is one possible solution:
    #!/usr/local/bin/perl

    $count = 1;
    while ($count <= @ARGV) {
    print ("File $ARGV[$count-1]:");
    if (!(-e $ARGV[$count-1])) {
    print (" does not exist\n");
    } else {
    if (-r $ARGV[$count-1]) {
    print (" read");
    }
    if (-w $ARGV[$count-1]) {
    print (" write");
    }
    if (-x $ARGV[$count-1]) {
    print (" execute");
    }
    print ("\n");
    }
    $count++;
    }
  6. This program is opening outfile in read mode, not write mode. To open in write mode, change the call to open to
    open (OUTFILE, ">outfile");

Answers for Day 7, "Pattern Matching"

Quiz

  1. The answers are as follows:
    1. Either the letter a or b, followed by zero or more occurrences of c.
    2. One, two, or three digits.
    3. The words cat, cot, and cut. (This pattern does not match these letters if they are in the middle of a word.)
    4. The first part of this pattern matches a subpattern consisting of x, one or more of y, and z. The rest of the pattern then matches a period, followed by the subpattern first matched.
    5. This matches an empty line (the null string).
  2. The answers are as follows:
    1. /[a-z]{5,}/
    2. /1|one/
    3. /\d+\.?\d+/
    4. /([A-Za-z])[aeiou]\1/
    5. /\++/
  3. Items a, b, c, and f are true; d and e are false.
  4. The answers are as follows:
    1. "def123abc"
    2. "X123X"
    3. "aWc123abc"
    4. "abd"
    5. "abc246abc"
  5. The answers are as follows:
    1. "ABC123ABC"
    2. "abc456abc"
    3. "abc456abc"
    4. "abc abc"
    5. "123"

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl

    while ($line = <STDIN>) {
    $line =~ tr/aeiou/AEIOU/;
    print ($line);
    }
  2. Here is one possible solution:
    #!/usr/local/bin/perl

    while ($inputline = <STDIN>) {
    $inputline =~ tr/0-9/ /c;
    $inputline =~ s/ +//g;
    @digits = split(//, $inputline);
    $total += @digits;
    $count = 1;
    while ($count <= @digits) {
    $dtotal[$digits[$count-1]] += 1;
    $count++;
    }
    }
    print ("Total number of digits found: $total\n");
    print ("Breakdown:\n");
    $count = 0;
    while ($count <= 9) {
    if ($dtotal[$count] > 0) {
    print ("\tdigit $count: $dtotal[$count]\n");
    }
    $count++;
    }
  3. Here is one possible solution:
    #!/usr/local/bin/perl

    while ($line = <STDIN>) {
    $line =~ s/(\w+)(\s+)(\w+)(\s+)(\w+)/$5$2$3$4$1/;
    print ($line);
    }
  4. Here is one possible solution:
    #!/usr/local/bin/perl

    while ($line = <STDIN>) {
    $line =~ s/\d+/$&+1/eg;
    print ($line);
    }
  5. There are two problems. The first is that the pattern matches the entire line, including the closing newline. You do not want to put a quotation mark after the closing newline of each line. In this case, it causes the program to omit the s operator, which specifies substitution.
    The second problem is that the replacement string should contain $1, not \1. \1 is defined only inside the search pattern.
  6. The pattern uses the * special character, which matches zero or more occurrences of any digit. This means the pattern always matches.
    The pattern should use the + special character, which matches one or more occurrences of any digit.

Answers for Day 8, "More Control Structures"

Quiz

  1. 7
  2. 11
  3. 5
  4. 6
  5. last if ($x eq "done");
  6. redo if ($list[0] == 26);
  7. next LABEL if ($scalar eq "#");
  8. print ("$count\n") while ($count++ < 10);
  9. The continue statement defines a block of code to be executed each time a while or until statement loops.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $count = 1;
    
    do {
    
            print ("$count\n");
    
            $count++;
    
    } while ($count <= 10);
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($count = 1; $count <= 10; $count++) {
    
            print ("$count\n");
    
    }
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($count = 1; $count <= 5; $count++) {
    
            $line = <STDIN>;
    
            last if ($line eq "");
    
            print ($line);
    
    }
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($count = 1; $count <= 20; $count++) {
    
            next if ($count % 2 == 1);
    
            print ("$count\n");
    
    }
    
    
  5. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $linenum = 0;
    
    while ($line = <STDIN>) {
    
            $linenum += 1;
    
            $occurs = 0;
    
            $line =~ tr/A-Z/a-z/;
    
            @words = split(/\s+/, $line);
    
            foreach $word (@words) {
    
                    $occurs += 1 if ($word eq "the");
    
            }
    
            if ($occurs > 0) {
    
                    print ("line $linenum: $occurs occurrences\n");
    
            }
    
    }
    
    
  6. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $count = 10;
    
    while ($count >= 1) {
    
            print ("$count\n");
    
    }
    
    continue {
    
            $count-;
    
    }
    
    
  7. You can't use the last statement inside a do statement. To get around this problem, use another loop construct such as while or for, or put the conditional expression in the while statement at the bottom.

Answers for Day 9, "Using Subroutines"

Quiz

  1. The answers are as follows:
    1. A subroutine is a separate body of code designed to perform a particular task.
    2. An invocation is a statement that tells the Perl interpreter to execute a particular subroutine.
    3. An argument is a value that is passed to a subroutine when it is invoked.
    4. A single-exit module is a subroutine whose return value is calculated by the final statement in the subroutine.
    5. Aliasing occurs when one name is defined to be equivalent to another.
  2. The answers are as follows:
    1. 0
    2. (4, 5, 6)
    3. (4, 5, 6)
  3. False (or zero), because the conditional expression $count <= 10 is the last expression evaluated in the subroutine.
  4. (1, 5, 3)

Exercises

  1. Here is one possible solution:
    sub add_two {
    
            local ($arg1, $arg2) = @_;
    
    
    
            $result = $arg1 + $arg2;
    
    }
    
    
  2. Here is one possible solution:
    sub count_t {
    
            local ($string) = @_;
    
    
    
            # There are a couple of tricks you can use to do this.
    
            # This one splits the string into words using "t" as
    
            # the split pattern. The number of occurrences of "t"
    
            # is one less than the number of words resulting from
    
            # the split.
    
            @dummy = split(/t/, $string);
    
            $retval = @dummy - 1;
    
    }
    
    
  3. Here is one possible solution:
    sub diff {
    
            local ($file1, $file2) = @_;
    
    
    
            # return false if we can't open a file
    
            return (0) unless open (FILE1, "$file1");
    
            return (0) unless open (FILE2, "$file2");
    
            while (1) {
    
                    $line1 = <FILE1>;
    
                    $line2 = <FILE2>;
    
                    if ($line1 eq "") {
    
                            $retval = ($line2 eq "");
    
                            last;
    
                    }
    
                    if ($line2 eq "" || $line1 ne $line2) {
    
                            $retval = 0;
    
                            last;
    
                    }
    
            }
    
            # you should use close here, as this subroutine may
    
            # be called many times
    
            close (FILE1);
    
            close (FILE2);
    
            # ensure that the return value is the last evaluated
    
            # expression
    
            $retval;
    
    }
    
    
  4. Here is one possible solution:
    sub dieroll {
    
            $retval = int (rand(6)) + 1;
    
    }
    
    
  5. Here is one possible solution:
    # assume that the first call to printlist passes the argument
    
    # 0 as the value for $index
    
    sub printlist {
    
            local ($index, @list) = @_;
    
    
    
            if ($index + 1 < @list) {
    
                    &printlist ($index+1, @list);
    
            }
    
            # the conditional handles the case of an empty list
    
            print ("$list[$index]\n") if (@list > 0);
    
    }
    
    
  6. The subroutine print_ten overwrites the value stored in the global variable $count. To fix this problem, define $count as a local variable. (You also should define $printval as a local variable, in case someone adds this variable to the main program at a later time.)
  7. The local statement in the subroutine assigns both the list and the search word to @searchlist, which means that $searchword is assigned the empty string. To fix this problem, switch the order of the arguments, putting the search word first.
  8. If split produces a nonempty list, the last expression evaluated in the subroutine is the conditional expression, which has the value 0 (false):
    @words == 0
    Therefore, the return value of this subroutine is 0, not the list of words.
    To get around this problem, put the following statement after the if statement:
    @words;
    This ensures that the list of words is always the return value.

Answers for Day 10, "Associative Arrays"

Quiz

  1. The answers are as follows:
    1. An associative array is an array whose subscripts can be any scalar value.
    2. A pointer is an associative array element whose value is the subscript of another associative array element.
    3. A linked list is an associative array in which each element of the array points to the next.
    4. A binary tree is a data structure in which each element points to (at most) two other elements.
    5. A node is an element of a binary tree.
    6. A child is an element of a binary tree that is pointed to by another element.
  2. This statement creates an associative array containing three elements:
  3. When you assign an associative array to an ordinary array variable, the value of the array variable becomes a list consisting of all of the subscript/value pairs of the associative array (in the order in which they were stored in the associative array, which is random).
  4. Define a scalar variable containing the value of the list's first element. Then, use the value of one associative array element as the subscript for the next.
  5. This is a trick question: Because the associative array %list stores its elements in random order, it is not clear how many times the foreach loop iterates. It could be one, two, or three.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    while ($line = <STDIN>) {
    
             $line =~ s/^\s+|\s+$//g;
    
             ($subscript, $value) = split(/\s+/, $line);
    
             $array{$subscript} = $value;
    
    }
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $linenum = 0;
    
    while ($line = <STDIN>) {
    
            $linenum += 1;
    
            $line =~ s/^\s+|\s+$//g;
    
            @words = split(/\s+/, $line);
    
            if ($words[0] eq "index" &&
    
                    $index{$words[1]} eq "") {
    
                    $index{$words[1]} = $linenum;
    
            }
    
    }
    
    foreach $item (sort keys (%index)) {
    
            print ("$item: $index{$item}\n");
    
    }
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $linenum = 0;
    
    while ($line = <STDIN>) {
    
            $linenum += 1;
    
            $line =~ s/^\s+|\s+$//g;
    
            @words = split(/\s+/, $line);
    
            # This program uses a trick: for each word, the array
    
            # item $index{"word"} stores the number of occurrences
    
            # of that word. Each occurrence is stored in the
    
            # element $index{"word#n"}, where[]is a
    
            # positive integer.
    
            if ($words[0] eq "index") {
    
                    if ($index{$words[1]} eq "") {
    
                            $index{$words[1]} = 1;
    
                            $occurrence = 1;
    
                    } else {
    
                            $index{$words[1]} += 1;
    
                            $occurrence = $index{$words[1]};
    
                    }
    
                    $index{$words[1]."#".$occurrence} = $linenum;
    
            }
    
    }
    
    
    
    # The loop that prints the index takes advantage of the fact
    
    # that, when the list is sorted, the elements that count
    
    # occurrences are always processed just before the
    
    # corresponding elements that store occurrences. For example:
    
    # $index{word}
    
    # $index{word#1}
    
    # $index{word#2}
    
    foreach $item (sort keys (%index)) {
    
            if ($item =~ /#/) {
    
                    print ("\n$item:");
    
            } else {
    
                    print (" $index{$item}");
    
            }
    
    }
    
    print ("\n");
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $student = 0;
    
    @subjects = ("English", "history", "mathematics",
    
                 "science", "geography");
    
    while ($line = <STDIN>) {
    
            $line =~ s/^\s+|\s+$//g;
    
            @words = split (/\s+/, $line);
    
            @students[$student++] = $words[0];
    
    for ($count = 1; $count <= 5; $count++) {
    
                    $marks{$words[0].$subjects[$count-1]} =
    
                         $words[$count];
    
            }
    
    }
    
    
    
    # now print the failing grades, one student per line
    
    foreach $student (sort (@students)) {
    
           $has_failed = 0;
    
            foreach $subject (sort (@subjects)) {
    
                    if ($marks{$student.$subject} < 50) {
    
                            if ($has_failed == 0) {
    
                                    $has_failed = 1;
    
                                    print ("$student failed:");
    
                            }
    
                            print (" $subject");
    
                    }
    
            }
    
            if ($has_failed == 1) {
    
                    print ("\n");
    
            }
    
    }
    
    
  5. This program has one problem and one unwanted feature.
    The problem: Adding a new element to %list in the middle of a foreach loop that uses the function keys yields unpredictable results.
    The unwanted feature: The foreach loop doubles the size of the associative array because the original elements Fred, John, Jack, and Mary are not deleted.

Answers for Day 11, "Formatting Your Output"

Quiz

  1. The answers are as follows:
    1. @<<<<<<<<<
    2. @>>>>
    3. @|
    4. @####.###
    5. ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  2. The answers are as follows:
    1. Five left-justified characters.
    2. Seven centered characters.
    3. One character.
    4. Multiple (unformatted) lines of text.
    5. Ten right-justified characters, with the line being printed only if the line is not blank.
  3. The answers are as follows:
    1. An integer (base 10) in a field of at least five digits.
    2. A floating-point number with a total field width of 11 characters, four of which are to the right of the decimal point.
    3. A base-10 integer in a field of at least 10 digits. Empty characters in the field are filled with zeroes.
    4. A character string of at least 12 characters, left-justified.
    5. An integer in hexadecimal (base-16) form.
  4. Numbers with rounding problems are numbers that normally round up but cannot be exactly stored on the machine. The closest equivalent that can be stored rounds down.
  5. To create a page header for an output file, define a print format named filename_TOP, where filename is the file variable associated with the file. (Or, you can create a print format of any name and assign the name to the system variable $^.)

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($count = 1; $count <= 9; $count += 3) {
    
            $num1 = 2 ** $count;
    
            $num2 = 2 ** ($count + 1);
    
            $num3 = 2 ** ($count + 2);
    
            write;
    
    }
    
    $num1 = 2 ** 10;
    
    $num2 = $num3 = "";
    
    write;
    
    
    
    format STDOUT =
    
    ^>>> ^>>> ^>>>
    
    $num1 $num2 $num3
    
    .
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($count = 1; $count <= 10; $count++) {
    
            printf ("%4d", 2 ** $count);
    
            if ($count % 3 == 0) {
    
                    print ("\n");
    
            } else {
    
                    print (" ");
    
            }
    
    }
    
    print ("\n");
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    @text = <STDIN>;
    
    $line = join("", @text);
    
    write;
    
    format STDOUT =
    
    ******************************************
    
    ~~^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    
    $line
    
    ******************************************
    
    .
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    $total1 = $total2 = 0;
    
    while (1) {
    
            $num1 = <STDIN>;
    
            last if ($num1 eq "");
    
            chop ($num1);
    
            $num2 = <STDIN>;
    
            last if ($num2 eq "");
    
            chop ($num2);
    
            $~ = "LINE";
    
            write;
    
            $total1 += $num1;
    
            $total2 += $num2;
    
    }
    
    $~ = "TOTAL";
    
    write;
    
    $~ = "GRAND_TOTAL";
    
    write;
    
    
    
    format LINE =
    
                        @####.##   @####.##
    
                        $num1      $num2
    
    .
    
    format TOTAL =
    
       column totals:  @#####.##  @#####.##
    
                        $total1    $total2
    
    .
    
    format GRAND_TOTAL =
    
    grand total:                  @#####.##
    
                        $total1 + $total2
    
    .
    
    
  5. When print writes a line to the page, the $- variable is not automatically updated. This means that the line count is off. To fix this, subtract one from the $- variable yourself.
    Also, you must specify what is to be printed by the STDOUT print format:
    format STDOUT =
    @*
    $line
    .

Answers for Day 12, "Working with the File System"

Quiz

  1. The answers are as follows:
    1. The tell function returns the current location in the file being read (the location of the next line to read).
    2. The mkdir function creates a directory.
    3. The link function creates a hard link (defines a second name that refers to a particular file).
    4. The unlink function destroys a hard link (the connection between the filename and the file). If no additional hard links have been defined (using link), unlink deletes the file.
    5. The truncate function reduces the size of a file to the length specified.
  2. lstat assumes that the name it is working with is a symbolic link. stat assumes it is working with an actual file.
  3. tell retrieves the location of the next line to be read in a file. telldir retrieves the location of the next name to be read in a directory.
  4. The answers are as follows:
    1. file1 is open for reading only.
    2. file2 is actually a pipe that is sending the output from a command to this program (where it is treated as input).
    3. file3 is open for reading and writing. (+<file3 is equivalent.)
    4. MYFILE is being treated as identical to STDOUT (the two file variables now refer to the same file).
  5. The answers are as follows:
    1. Read and write permissions for everybody.
    2. Read, write, and execute permissions for everybody.
    3. Read, write, and execute permissions for the owner only.
    4. Read and write permissions for the owner; read permissions for everybody else.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    opendir (MYDIR, "/u/jqpublic") ||
    
            die ("Can't open directory");
    
    while ($file = readdir (MYDIR)) {
    
            next if ($file =~ /^\.{1,2}$|^[^.]/);
    
            print ("$file\n");
    
    }
    
    closedir (MYDIR);
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    $filecount = 1;
    
    &print_dir ("/u/jqpublic");
    
    
    
    sub print_dir {
    
            local ($dirname) = @_;
    
            local ($file, $subdir, $filevar);
    
    
    
            $filevar = "MYFILE" . $filecount++;
    
            opendir ($filevar, $dirname) ||
    
                    die ("Can't open directory");
    
            # first pass: read and print file names
    
            print ("\ndirectory $dirname:\n");
    
            while ($file = readdir ($filevar)) {
    
                    next if ($file eq "." || $file eq "..");
    
                    next if (-d ($dirname . "/" . $file));
    
                    print ("$file\n");
    
            }
    
            # second pass: recursively print subdirectories
    
            rewinddir ($filevar);
    
            while ($subdir = readdir ($filevar)) {
    
                    next unless (-d ($dirname . "/" . $subdir));
    
                    next if ($subdir eq "." || $subdir eq "..");
    
                    &print_dir ($dirname . "/" . $subdir);
    
            }
    
            closedir ($filevar);
    
    }
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    opendir (MYDIR, "/u/jqpublic") ||
    
            die ("Can't open directory");
    
    # the following is a trick: "." is alphabetically less than
    
    # anything we want to print, so it makes a handy
    
    # initial value
    
    $lastfile = ".";
    
    until (1) {
    
            rewinddir (MYDIR);
    
            $currfile = "";
    
            while ($file = readdir (MYDIR)) {
    
                    next if ($file =~ /^\./);
    
                    if ($file gt $lastfile &&
    
                       ($currfile eq "" || $file lt $currfile)) {
    
                            $currfile = $file;
    
                    }
    
            }
    
            last if ($currfile eq "");
    
            print ("$currfile\n");
    
            $lastfile = $currfile;
    
    }
    
    closedir (MYDIR);
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    @digits = ("zero", "one", "two", "three",
    
               "four", "five", "six", "seven",
    
               "eight", "nine");
    
    &start_hot_keys;
    
    while (1) {
    
            $char = getc(STDIN);
    
            last if ($char eq "\033");
    
            next if ($char =~ /[^0-9]/);
    
            print ("$digits[$char]\n");
    
    }
    
    &end_hot_keys;
    
    
    
    sub start_hot_keys {
    
            system ("stty cbreak");
    
            system ("stty -echo");
    
    }
    
    
    
    sub end_hot_keys {
    
            system ("stty -cbreak");
    
            system ("stty echo");
    
    }
    
    
  5. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $dir = "/u/dave/newperl/testdir";
    
    opendir (MYDIR, $dir) ||
    
            die ("Can't open directory");
    
    chdir ($dir);
    
    while ($file = readdir (MYDIR)) {
    
            next if (-d $file);
    
            next if ($file eq "." || $file eq "..");
    
            if ($file =~ /\.pl$/) {
    
                    @stat = stat($file);
    
                    chmod (($stat[2] | 0700), $file);
    
            } else {
    
                    chmod (0400, $file);
    
            }
    
    }
    
    closedir (MYFILE);
    
    
  6. This program is trying to use eof() to test for the end of a particular input file. In Perl, eof() tests for the end of the entire set of input files, and eof (with no parentheses) tests for the end of a particular input file.

Answers for Day 13, "Process, String, and Mathematical Functions"

Quiz

  1. The answers are as follows:
    1. srand provides a seed for the random number generator.
    2. pipe creates an input file variable and output file variable that are linked together. This is most frequently used by fork to allow processes to communicate with one another.
    3. atan2 calculates the arctangent for a particular value (in the range -p to p).
    4. sleep suspends the program for a specified number of seconds.
    5. gmtime returns the current Greenwich Mean Time (in machine-readable format).
  2. fork starts an identical copy of the program currently running. system starts a completely different program that runs concurrently (at the same time as the current program). exec terminates the current program and starts a new one.
  3. wait waits for any child process to terminate. waitpid waits for a particular child process to terminate.
  4. The easiest way to obtain the value of p is with the following statement:
    $pi = atan2(1, 1) * 4;
    This normally produces as close an approximation of p as is possible on your system.
  5. You can obtain e with this statement: $e = exp(1);
  6. The answers are as follows:
    1. %x
    2. %o
    3. %e
    4. %f
  7. The answers are as follows:
    1. "abc"
    2. "efgh"
    3. "gh"
    4. The null string (a length of 0 is being specified)
  8. The answers are as follows:
    1. 1
    2. -1
    3. 1
    4. 6
    5. 5
  9. The answers are as follows:
    1. 4
    2. 9

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $child = fork();
    
    if ($child == 0) {
    
            print ("This line goes first\n");
    
            exit (0);
    
    } else {
    
            $child2 = fork();
    
            if ($child2 == 0) {
    
                    waitpid ($child, 0);
    
                    print ("This line goes second\n");
    
                    exit (0);
    
            } else {
    
                    waitpid ($child2, 0);
    
                    print ("This line goes third\n");
    
            }
    
    }
    
    
  2. Here is a program that reads from temp:
    #!/usr/local/bin/perl

    open (INFILE, "temp") || die ("Can't open input");
    while ($line = <INFILE>) {
    print ($line);
    }
    close (INFILE);
    Here is a program that writes to temp and calls the first program (which is assumed to be named ch13.2a):
    #!/usr/local/bin/perl
    
    
    
    open (OUTFILE, ">temp") || die ("Can't open output");
    
    while ($line = <STDIN>) {
    
            print OUTFILE ($line);
    
    }
    
    close (OUTFILE);
    
    exec ("ch13.2a");
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($val = 1; $val <= 100; $val++) {
    
            print ("log of $val is ", log($val), "\n");
    
    }
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    for ($i = 1; $i <= 6; $i++) {
    
            &sum(10 ** $i);
    
    }
    
    
    
    sub sum {
    
            local($limit) = @_;
    
            local(@startval, @stopval);
    
            local($i, $count);
    
    
    
            $count = 0;
    
            @startval = times();
    
            for ($i = 1; $i <= $limit; $i++) {
    
                    $count += $i;
    
            }
    
            @stopval = times();
    
            print ("sum $limit: ", $stopval[0]-$startval[0], "\n");
    
    }
    
    
  5. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $degrees = <STDIN>;
    
    chop ($degrees);
    
    $radians = $degrees * atan2(1,1) / 45;
    
    $sin = sin ($radians);
    
    $cos = cos ($radians);
    
    print ("sin of $degrees is ", $sin, "\n");
    
    print ("cos of $degrees is ", $cos, "\n");
    
    print ("tan of $degrees is ", $sin/$cos, "\n");
    
    
  6. The output specified by the first call to print might get jumbled because the call to system defines its own standard output buffers. To get around this problem, set the system variable $| to 1 before calling system.
  7. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    @searchletters = ("a", "e", "i", "o", "u");
    
    $inputline = <STDIN>;
    
    foreach $letter (@searchletters) {
    
            printf("searching for $letter...\n");
    
            $location = 0;
    
            while (1) {
    
                    $location = index ($inputline, $letter,
    
                                $location);
    
                    last if ($location == -1);
    
                    print("\tfound at location $location\n");
    
                    $location += 1;
    
            }
    
    }
    
    
  8. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    @searchletters = ("a", "e", "i", "o", "u");
    
    $inputline = <STDIN>;
    
    foreach $letter (@searchletters) {
    
            printf("searching for $letter...\n");
    
            $location = length ($inputline);
    
            while (1) {
    
                    $location = rindex ($inputline, $letter,
    
                                        $location);
    
                    last if ($location == -1);
    
                    print("\tfound at location $location\n");
    
                    $location -= 1;
    
            }
    
    }
    
    
  9. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    @searchletters = ("a", "e", "i", "o", "u");
    
    $inputline = <STDIN>;
    
    $len = length ($inputline);
    
    foreach $letter (@searchletters) {
    
            print ("searching for $letter...\n");
    
            $currpos = 0;
    
            while ($currpos < $len) {
    
                    $substring = substr ($inputline, $currpos, 1);
    
                    if ($letter eq $substring) {
    
                           print("\tfound at location $currpos\n");
    
                    }
    
                    $currpos++;
    
            }
    
    }
    
    
  10. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $_ = <STDIN>;   # reads to $_ by default
    
    print ("number of a's found: ", tr/a/a/, "\n");
    
    print ("number of e's found: ", tr/e/e/, "\n");
    
    print ("number of i's found: ", tr/i/i/, "\n");
    
    print ("number of o's found: ", tr/o/o/, "\n");
    
    print ("number of u's found: ", tr/u/u/, "\n");
    
    
  11. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $number = <STDIN>;
    
    if ($number =~ /\.|[eE]/) {
    
            printf ("in exponential form: %e\n", $number);
    
            printf ("in fixed-point form: %f\n", $number);
    
    } else {
    
            printf ("in decimal form: %d\n", $number);
    
            printf ("in octal form: 0%o\n", $number);
    
            printf ("in hexadecimal form: 0x%x\n", $number);
    
    }
    
    
  12. This program goes into an infinite loop if index actually finds the substring xyz. To get around this problem, increment $lastfound (at the bottom of the loop) before calling index again.

Answers for Day 14, "Scalar-Conversion and List-Manipulation Functions"

Quiz

  1. The answers are as follows:
    1. A character string, padded with null characters if necessary.
    2. A character string, padded with blanks if necessary.
    3. A floating-point number (double-precision).
    4. A pointer to a string (as in the C programming language).
    5. Skip to the position specified.
  2. The answers are as follows:
    1. Unpack a character string (unstripped).
    2. Skip four bytes, unpack a 10-character string (stripping null characters and blanks), and then treat the rest of the packed string as integers.
    3. Skip to the end of the packed string, back up four bytes, and unpack four unsigned characters.
    4. Unpack the first integer, skip four bytes, unpack an integer, skip back eight bytes, and unpack another integer. (This, effectively, unpacks the first, third, and second integers in that order.)
    5. Unpack a string of bits in low-to-high order, back up to the beginning, and unpack the same string of bits in high-to-low order.
  3. The answers are as follows:
    1. 1
    2. 3
  4. defined tests whether a particular value is equivalent to the special "undefined" value. undef sets a scalar variable, array element, or array variable to be equal to the special undefined value.
  5. The answers are as follows:
    1. ("new", "2", "3", "4", "5")
    2. ("1", "2", "test1", "test2", "3", "4", "5")
    3. ("1", "2", "3", "4", "test1", "test2")
    4. ("1", "2", "4", "5")
    5. ("1", "2", "3")
  6. The answers are as follows:
    1. This returns every list element that does not start with an exclamation mark.
    2. This returns every list element that contains a word that consists entirely of digits.
    3. This returns every nonempty list element.
    4. This returns every list element.
  7. unshift adds one or more elements to the left end of a list. shift removes an element from the left end of a list.
  8. The answers are as follows:
    1. splice (@array, 0, 1);
    2. splice (@array, @array-1, 1);
    3. splice (@array, scalar(@array), 0, @sublist);
    4. splice (@array, 0, 0, @sublist);
  9. You can create a stack using push to add elements and pop to remove them (or by using shift and unshift in the same way).
  10. You can create a queue using push to add elements and shift to remove them (or by using unshift and pop in the same way).

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    $string1 = <STDIN>;
    
    chop ($string1);
    
    $len1 = length ($string1);
    
    $string2 = <STDIN>;
    
    chop ($string2);
    
    $len2 = length ($string2);
    
    if ($len1 % 8 != 0) {
    
            $string1 = "0" x (8 - $len1 % 8) . $string1;
    
            $len1 += 8 - $len1 % 8;
    
    }
    
    if ($len2 % 8 != 0) {
    
            $string2 = "0" x (8 - $len2 % 8) . $string2;
    
            $len2 += 8 - $len2 % 8;
    
    }
    
    if ($len1 > $len2) {
    
            $string2 = "0" x ($len1 - $len2) . $string2;
    
    } else {
    
            $string1 = "0" x ($len2 - $len1) . $string1;
    
            $len1 += ($len2 - $len1);
    
    }
    
    $bytes1 = pack ("b*", $string1);
    
    $bytes2 = pack ("b*", $string2);
    
    $carry = 0;
    
    $count = $len1 - 1;
    
    while ($count >= 0) {
    
            $bit1 = vec ($bytes1, $count, 1);
    
            $bit2 = vec ($bytes2, $count, 1);
    
            $result = ($bit1 + $bit2 + $carry) & 1;
    
            $carry = ($bit1 + $bit2 + $carry) >> 1;
    
            vec ($bytes1, $count, 1) = $result;
    
            $count-;
    
    }
    
    $resultstring = unpack ("b*", $bytes1);
    
    $resultstring = $carry . $resultstring if ($carry > 0);
    
    print ("$resultstring\n");
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    $string1 = <STDIN>;
    
    chop ($string1);
    
    $len1 = length ($string1);
    
    $string2 = <STDIN>;
    
    chop ($string2);
    
    $len2 = length ($string2);
    
    if ($len1 % 8 != 0) {
    
            $string1 = "0" x (8 - $len1 % 8) . $string1;
    
            $len1 += 8 - $len1 % 8;
    
    }
    
    if ($len2 % 8 != 0) {
    
            $string2 = "0" x (8 - $len2 % 8) . $string2;
    
            $len2 += 8 - $len2 % 8;
    
    }
    
    if ($len1 > $len2) {
    
            $string2 = "0" x ($len1 - $len2) . $string2;
    
    } else {
    
            $string1 = "0" x ($len2 - $len1) . $string1;
    
            $len1 += ($len2 - $len1);
    
    }
    
    $bytes1 = pack ("h*", $string1);
    
    $bytes2 = pack ("h*", $string2);
    
    $carry = 0;
    
    $count = $len1 - 1;
    
    while ($count >= 0) {
    
            $nybble1 = vec ($bytes1, $count, 4);
    
            $nybble2 = vec ($bytes2, $count, 4);
    
            $result = ($nybble1 + $nybble2 + $carry) & 15;
    
            $carry = ($nybble1 + $nybble2 + $carry) >> 4;
    
            vec ($bytes1, $count, 4) = $result;
    
            $count-;
    
    }
    
    $resultstring = unpack ("h*", $bytes1);
    
    $resultstring = $carry . $resultstring if ($carry > 0);
    
    print ("$resultstring\n");
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $value = <STDIN>;
    
    $value *= 100;
    
    $value = int ($value + 0.5);
    
    $value = sprintf ("%.2f", $value / 100);
    
    print ("$value\n");
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $passwd = crypt ("bluejays", "ez");
    
    $try = 1;
    
    while (1) {
    
            print ("Enter the secret password:\n");
    
            system ("stty -echo");
    
            $guess = <STDIN>;
    
            system ("stty echo");
    
            if (crypt ($guess, substr ($passwd, 0, 2))
    
                    eq $passwd) {
    
                    print ("Correct!\n");
    
                    last;
    
            }
    
            if ($try == 3) {
    
                    die ("Sorry! Goodbye!\n");
    
            }
    
            print ("Try again - ");
    
            $try++;
    
    }
    
    
  5. This program is actually reading the low-order bit of the bit vector. To read the high-order bit, use vec ($packed, 7, 1).
  6. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    # This program uses a very dumb sorting algorithm.
    
    @list = (41, 26, 11, 9, 8);    # sample list to sort
    
    for ($outer = 0; $outer < @list; $outer++) {
    
            for ($inner = 0; $inner < @list; $inner++) {
    
                    if ($list[$inner] > $list[$inner+1]) {
    
                            $x = splice (@list, $inner, 1);
    
                            splice (@list, $inner+1, 0, $x);
    
                    }
    
            }
    
    }        
    
    
  7. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    # assume %oldarray is assigned here
    
    while (($subscript, $value) = each (%oldarray)) {
    
            if (defined ($newarray{$value})) {
    
                    print STDERR ("$value already defined\n");
    
            } else {
    
                    $newarray{$value} = $subscript;
    
            }
    
    }
    
    
  8. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    while ($line = <STDIN>) {
    
            @words = split (/\s+/, $line);
    
            @shortwords = grep (/^.{1,5}$/, @words);
    
            print ("@shortwords\n");
    
    }
    
    
  9. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $line = <STDIN>;
    
    $line =~ s/^\s+//;
    
    while (1) {
    
            last if ($line eq "");
    
            ($word, $line) = split (/\s+/, $line, 2);
    
            print ("$word\n");
    
    }
    
    
  10. This subroutine is trying to remove an element from a list using unshift. The subroutine should use shift, not unshift.

Answers for Day 15, "System Functions"

Quiz

  1. The answers are as follows:
    1. endpwent, getpwent, getpwnam, getpwuid, and setpwent.
    2. endhostent, gethostbyaddr, gethostbyname, gethostent, and sethostent.
    3. endnetent, getnetbyaddr, getnetbyname, getnetent, and setnetent.
    4. endservent, getservbyname, getservbyport, getservent, and setservent.
  2. Server processes call socket, bind, listen, and accept, in that order. Client processes call socket, bind, and connect, in that order.
  3. The answers are as follows:
    1. getpwuid searches for an entry in /etc/passwd that matches a specific user ID.
    2. setprotoent rewinds the /etc/protocols file.
    3. gethostbyaddr searches the /etc/hosts file for a particular network (Internet) address.
    4. getgrent retrieves the next entry from the /etc/group file.
    5. getservbyport searches the /etc/services file for an entry corresponding to a particular port number.
  4. To send information using a socket, use an output function such as print or printf, and specify the file variable associated with the socket.
  5. You can obtain all the user IDs on your system by using getpwent to read the /etc/passwd file. This file contains one entry per user ID, and the user ID is part of the entry.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    while (($gname, $password, $groupid, $userids)
    
            = getgrent()) {
    
            $garray{$gname} = $userids;
    
    }
    
    foreach $gname (sort keys (%garray)) {
    
            print ("Group $gname:\n");
    
            @userids = split (/\s+/, $garray{$gname});
    
            foreach $userid (sort (@userids)) {
    
                    print ("\t$userid\n");
    
            }
    
    }
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    while (($name, $d1, $d2, $d3, $d4, $d5, $d6, $homedir) =
    
            getpwent()) {
    
            $dirlist{$name} = $homedir;
    
    }
    
    foreach $name (sort keys (%dirlist)) {
    
            printf ("userid %-15s has home directory %s\n",
    
                    $name, $dirlist{$name});
    
    }
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    while (@retval = getpwent()) {
    
             $retval[8] = "<null>" if ($retval[8] eq "");
    
             $shellarray{$retval[8]} += 1;
    
    }
    
    foreach $shell (sort count keys (%shellarray)) {
    
            printf ("%-25s %5d %s\n", $shell, $shellarray{$shell},
    
                    ($shellarray{$shell} == 1 ?
    
                    "occurrence" : "occurrences"));
    
    }
    
    sub count {
    
            $shellarray{$b} <=> $shellarray{$a};
    
    }
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $otherid = fork();
    
    if ($otherid == 0) {
    
            # child process
    
            $otherid = getppid();
    
    }
    
    $| = 1;  # eliminate print buffers
    
    print ("The process id of the other process is $otherid.\n");
    
    
  5. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $port = 2000;
    
    while (getservbyport($port, "tcp")) {
    
            $port++;
    
    }
    
    ($d1, $d2, $prototype) = getprotobyname ("tcp");
    
    # in the following, replace "silver" with the name
    
    # of your machine
    
    ($d1, $d2, $d3, $d4, $rawaddr) = gethostbyname ("silver");
    
    $serveraddr = pack ("Sna4x8", 2, $port, $rawaddr);
    
    socket (SSOCKET, 2, 1, $prototype) || die ("No socket");
    
    bind (SSOCKET, $serveraddr) || die ("Can't bind");
    
    listen (SSOCKET, 5) || die ("Can't listen");
    
    while (1) {
    
            ($clientaddr = accept (SOCKET, SSOCKET))
    
            || die ("Can't accept");
    
            if (fork() == 0) {
    
                    select (SOCKET);
    
                    $| = 1;
    
                    open (MYFILE, "/u/jqpublic/testfile");
    
                    while ($line = <MYFILE>) {
    
                            print SOCKET ($line);
    
                    }
    
                    close (MYFILE);
    
                    close (SOCKET);
    
                    exit (0);
    
            }
    
    }
    
    
  6. getnetent returns an address as an array of four bytes, not as a readable address. To convert the address returned by getnetent to readable form, call unpack.

Answers for Day 16, "Command-Line Options"

Quiz

  1. The answers are as follows:
    1. The -0 option specifies the end of file character for the input line.
    2. The -s option enables you to specify options for your program.
    3. The -w option tells the Perl interpreter to warn you if it sees something that it thinks is erroneous.
    4. The -x option tells the Perl interpreter that your program is to be extracted from a file.
    5. The -n option indicates that each line of the files specified on the command line is to be read.
  2. The answers are as follows:
    1. The input end-of-line character becomes either newline or the character specified by -l. The output end-of-line character becomes either null or the character specified by -0.
    2. The input end-of-line character becomes either the character specified by -l or the character specified by -0; if neither option has a value supplied with it, the input line character becomes null. The output end-of-line character becomes either null or the character specified by -0.
  3. The -n option tells the Perl interpreter to read each line of the input file, but does not explicitly tell it to write out its input. The -i option copies the input file to a temporary file, and then opens the input file for writing. If you do not explicitly write to the file yourself, nothing gets written to it.
  4. This is a trick question: It doesn't. You'll have to make sure that your Perl comments are not C preprocessor commands.
  5. The options for the interpreter appear before the Perl program name in the command line, or in the header comment for the program. The options for the program appear after the program name.

Exercises

  1. Here is one possible solution:
    $ perl -i -p -l072 -e ";" testfile
    Note that -e ";" indicates an empty program. (Otherwise, the Perl interpreter would assume that testfile was the program, not the input file.)
  2. Here is one possible solution:
    $ perl -ne "print if (/\bthe\b/);" file1 file2 ...
    
    
  3. Here is one possible solution:
    $ perl -nae 'print ("$F[1]\n");' file1 file2 ...
    
    
  4. Here is one possible solution:
    #!/usr/local/bin/perl -s
    
    print ("Hello\n") if ($H == 1);
    
    print ("Goodbye\n") if ($G == 1);
    
    
  5. Here is one possible solution:
    $ perl -i -pe "tr/a-z/A-Z/;" file1 file2 ...
    
    
  6. This command line wipes out all of your input files. Use the -p option instead of the -n option.
  7. The -i option can be specified with a value (for creating a backup version of the file). The Perl interpreter thinks that pe is the suffix to append to the filename, and does not realize that these are supposed to be options. (I get tripped up by this problem all the time.)

Answers for Day 17, "System Variables"

Quiz

  1. The pattern-matching operator, the substitution operator, the translation operator, the <> operator (if it appears in a while or for conditional expression), the chop function, the print function, and the study function.
  2. The answers are as follows:
    1. The $= variable contains the page length of a particular output file.
    2. The $/ variable contains the input end-of-line character.
    3. The $? variable contains the return code returned by a command called by the system function or enclosed in back quotes.
    4. The $! variable contains the error code generated by a system library routine.
    5. The @_ variable contains the list of arguments passed to a subroutine by the calling program or calling subroutine.
  3. ARGV is the file variable used by the <> operator to read from the list of input files specified on the command line. $ARGV is the name of the current file being read by the <> operator. @ARGV is the list of arguments (or files) specified on the command line.
  4. @INC contains the directories to search when looking for files to be included. %INC lists the files requested by the require function that have already been found.
  5. $0 is the name of the program you are running. $1 is defined when a pattern is matched, and is the first subpattern enclosed in parentheses in the matched pattern.

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl -i

    while (<>) {
    s/[ \t]+/ /g;
    tr/A-Z/a-z/;
    print;
    }
    All of these statements use the system variable $_ by default.
  2. Here is one possible solution:
    #!/usr/local/bin/perl -i
    
    
    
    while ($line = <>) {
    
            while ($line =~ /  +/g) {
    
                    $line = $' . " " . $';
    
            }
    
            print ($line);
    
    }
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl

    @dirlist = split (/:/, $ENV{"PATH"});
    foreach $dir (@dirlist) {
    print ("$dir\n");
    }
    Note that if your machine uses a character other than : to separate entries in the value of your PATH environment variable, you should use this character instead.
  4. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $SIG{"INT"} = stopnum;
    
    $num = 1;
    
    while (1) {
    
            print ("$num\n");
    
            $num++;
    
    }
    
    
    
    sub stopnum {
    
            print ("\nInterrupted.\n");
    
            exit (0);
    
    }
    
    
  5. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    $total = 0;
    
    while ($line = <DATA>) {
    
            @nums = split (/\s+/, $line);
    
            foreach $num (@nums) {
    
                    $total += $num;
    
            }
    
    }
    
    print ("The total is $total.\n");
    
    __END__
    
    4 17  26
    
    11
    
    9     5
    
    
  6. The substitution operator matches a pattern, so it overwrites the value of $'. To fix this, copy $' into a scalar variable of your own before searching for extra spaces.

Answers for Day 20, "Miscellaneous Features of Perl"

Quiz

  1. The answers are as follows:
    1. __LINE__ contains the current line number of the executing program or subroutine.
    2. __FILE__ contains the current file being executed.
    3. __END__ indicates the end of the Perl program.
  2. The answers are as follows:
    1. It's time to say $var
    2. "It's time to say hello"; (including the quotes and the semicolon)
    3. hello
  3. ("one", "two", "three", "", "five")
  4. There are two ways:

Exercises

  1. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    @filelist = <*>;
    
    foreach $file (sort (@filelist)) {
    
            print ("$file\n");
    
    }
    
    
  2. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    unshift (@INC, "/u/jqpublic/perlfiles");
    
    require ("sum.pl");
    
    @numlist = <STDIN>;
    
    chop (@numlist);
    
    $total = &sum (@numlist);
    
    print ("The total is $total.\n"); 
    
    
  3. Here is one possible solution:
    #!/usr/local/bin/perl
    
    
    
    package pack1;
    
    $var = <STDIN>;
    
    chop ($var);
    
    package pack2;
    
    $var = <STDIN>;
    
    chop ($var);
    
    package main;
    
    $total = $pack1'var + $pack2'var;
    
    print ("The total is $total.\n");
    
    
  4. In this case, <$filepattern> is treated as a scalar variable containing the name of a file variable, not as a scalar variable containing a file list pattern. (To obtain the latter, use <${filepattern}>.)
  5. There should be no space between the << and the EOF. The space after the << means that the end-of-string character string is assumed to be null; therefore, print only prints the first of the two lines in the string.

Answers for Day 21, "The Perl Debugger"

Quiz

  1. The answers are as follows:
    1. Trace mode controls whether lines are displayed as they are executed. If trace mode is on, lines are displayed; if it is off, they are not.
    2. A stack trace is a display of the current subroutine being executed, plus a listing of the subroutine that called this one, and so on back to the original main program.
    3. A breakpoint is a line in the program before which execution is halted and further debugging commands are requested.
    4. A line action is a statement that is executed whenever a particular line of the program is reached.
  2. The X command displays only variables in the current package. The V command can display variables in any package.
  3. The // command searches forward in the file for a line matching the specified pattern; the ?? command searches backward.
  4. The > command defines a line action that is to be executed before the debugger executes any further statements. The < command defines a line action that is to be performed after the debugger has finished executing the next statement or group of statements.
  5. The s command steps into a subroutine when it encounters one; the n command executes the subroutine without stepping into it, stopping at the statement following the subroutine.
  6. The answers are as follows:
    1. This displays the next window of statements, continuing where the last l command left off.
    2. This displays just line 26.
    3. This displays lines 5-7.
    4. This displays lines 5-12.
    5. This displays the window of statements surrounding the current line.