Section A.10.  Answers to Chapter 11 Exercises

Table of Contents

A.10. Answers to Chapter 11 Exercises

  1. Here's one way to do it:

        foreach my $file (@ARGV) {
          my $attribs = &attributes($file);
          print "'$file' $attribs.\n";
        sub attributes {
          # report the attributes of a given file
          my $file = shift @_;
          return "does not exist" unless -e $file;
          my @attrib;
          push @attrib, "readable" if -r $file;
          push @attrib, "writable" if -w $file;
          push @attrib, "executable" if -x $file;
          return "exists" unless @attrib;
          'is ' . join " and ", @attrib;  # return value

    In this one, it's convenient to use a subroutine. The main loop prints one line of attributes for each file, perhaps telling us that 'cereal-killer' is executable or that 'sasquatch' does not exist.

    The subroutine tells us the attributes of the given filename. If the file doesn't exist, there's no need for the other tests, so we test for that first. If there's no file, we'll return early.

    If the file does exist, we'll build a list of attributes. (Give yourself extra credit points if you used the special _ filehandle instead of $file on these tests to keep from calling the system separately for each new attribute.) It would be easy to add additional tests like the three we show here. But what happens if none of the attributes is true? Well, if we can't say anything else, at least we can say that the file exists, so we do. The unless clause uses the fact that @attrib will be true (in a Boolean context, which is a special case of a scalar context) if it has any elements.

    If we have some attributes, we'll join them with "and" and put "is" in front, to make a description like is readable and writable. This isn't perfect however; if there are three attributes, it will say that the file is readable and writable and executable, which has too many ands, but we can get away with it. If you wanted to add more attributes to the ones this program checks for, you should probably fix it to say something like is readable, writable, executable, and nonempty. Do this if it matters to you.

    If you didn't put any filenames on the command line, this produces no output. This makes sense because if you ask for information on zero files, you should get zero lines of output. But let's compare that to what the next program does in a similar case, in the discussion below.

  2. Here's one way to do it:

        die "No file names supplied!\n" unless @ARGV;
        my $oldest_name = shift @ARGV;
        my $oldest_age = -M $oldest_name;
        foreach (@ARGV) {
          my $age = -M;
          ($oldest_name, $oldest_age) = ($_, $age)
            if $age > $oldest_age;
        printf "The oldest file was %s, and it was %.1f days old.\n",
          $oldest_name, $oldest_age;

    This one starts by complaining if it didn't get any filenames on the command line. That's because it's supposed to tell us the oldest filename, and there isn't one if there aren't any files to check.

    Once again, we're using the "high-water-mark" algorithm. The first file is certainly the oldest one seen so far. We have to keep track of its age as well, so that's in $oldest_age.

    For each of the remaining files, we'll determine the age with the -M file test as we did for the first one (except that here, we'll use the default argument of $_ for the file test). The last-modified time is generally what people mean by the "age" of a file, though you could make a case for using a different one. If the age is more than $oldest_age, we'll use a list assignment to update the name and age. We didn't have to use a list assignment, but it's a convenient way to update several variables at once.

    We stored the age from -M into the temporary variable $age. What would have happened if we had used -M each time rather than using a variable? Unless we used the special _ filehandle, we would have been asking the operating system for the age of the file each time, which is a potentially slow operation (not that you'd notice unless you have hundreds or thousands of files and maybe not even then). More importantly, we should consider what would happen if someone updated a file while we were checking it. That is, we see the age of some file, and it's the oldest one so far. But before we can get back to use -M a second time, someone modifies the file and resets the timestamp to the current time. Now, the age that we save into $oldest_age is actually the youngest age possible. The result would be that we'd get the oldest file among the files tested from that point on rather than the oldest overall; this would be a tough problem to debug.

    At the end of the program, we use printf to print out the name and age, with the age rounded off to the nearest tenth of a day. Give yourself extra credit if you went to the trouble to convert the age to a number of days, hours, and minutes.

    Table of Contents
    © 2000- NIV