0

Good afternoon, stackoverflow friends!

I have been given the following assignment and would like to learn how to see if a file or directory exists.

"Prompt the user to type in one parameter (note that this script only takes in one parameter and should report warnings/errors if the user input contains zero or multiple parameters), check if the input parameter is a filename or a directory name.

1) If the user input is a file, exit when the file is an empty file, and print the last line of the file.

2) If the user input is a directory, exit when the directory is empty, use a hash table to record the filenames inside this directory and the assess ages of these files (the number of days since the files were lastly assessed), find the oldest file (based on modification time) in the directory, and print the absolute path to this file."

I have spent the last 2 days looking a detailed explanation of how to use File::Find but cannot it. Below is my code and I'd like to replace -e $input sections with File::Find because with the -e file operator, I can only search in my current directory and not my entire system. I look forward to your replies and greatly appreciate your time.

#! /usr/bin/perl
# Assignment 9, check and read files and directories

use strict;
use warnings;
use Module1;

assnintro();

my @parameter;
my $input;

do {          # This loop keeps calling pamcheck subroutine until the 1 param entered
              # is a file in the directory, but I'd to expand the file test operator
              # to all directories
    $input     = ();
    @parameter = ();
    pamcheck( \@parameter );
    $input = $parameter[0];

    if ( -e $input ) { }    # Instead of simply checking current directory, I want to 
                            # I want to see that the file exists in my system
    else {
        print color 'red';
        print "The file or directory you entered, doesn't exist.\n";
        print color 'reset';
    }
} until ( -e $input );     

if ( -d $input ) {
    print "I am a directory.\n";
} else {
}

exit;

sub pamcheck {   # This subroutine asks user to to enter a file or directory name
    my ($parameter) = @_;     # In the terminal, "parameters" are separated by space
    my $elementcount;     # Do elem ct. to make sure usr only entered 1 parameter
    do {
        @$parameter = ();
        print "Enter one file or directory name: ";
        @_ = split( /\s+/, <> );     # "Enter" key act as Ctrl D to avoid infinite 
                                     # param entry
        push( @$parameter, @_ );
        $elementcount = 0;

        foreach (@_) {
            $elementcount++;
        }

        if ( $elementcount != 1 ) {      # Error msg if 1 param isn't entered
            print color 'red';
            print "Please enter only ONE parameter.\n";
            print color 'reset';
        } else {
        }
    } until ( $elementcount eq 1 );  # Loop continues until one param is entered
}
  • Thanks for the perl-tidy corrections. – MrStutterZ Nov 02 '14 at 19:39
  • 1
    If a post receives a negative score, it would be nice for the scorer to post why they are doing so. That way, the poster can correct the issue. A negative score only discourages others from viewing the post and that is unfair. I have spent hours looking to better understand File::Find. My 4th edition 'Programming Perl' book doesn't do the job and all sample codes online are commented well enough. Perhaps someone here could do a better job and help us who genuinely want to learn. Thank you. – MrStutterZ Nov 02 '14 at 20:01
  • 1
    `-e` checks for the existence of the file. File::Find is used to locate files by various criteria. They serve completely different functions, and there's no indication that either is needed to complete your assignment. Perhaps if you articulated what problem you are having? – ikegami Nov 02 '14 at 20:54
  • 1
    Tip: It's usually better just to try opening the file rather than checking if it's exists before trying to open it. `open` will tell you if it couldn't open the file (and it will even tell you why). – ikegami Nov 02 '14 at 20:57
  • Do you actually need to 'search' through a directory structure? This would make your assignment a lot more complex, as you'd have to handle duplicates. – Sobrique Nov 03 '14 at 14:16

1 Answers1

1

File::Find doesn't do what you think it does. It's based on the Unix find command which traverses a directory tree. (And may do stuff to all files in a directory tree).

The reason you need this is it's harder than you might think to do a deep traversal of a filesystem, in a world with symbolic links, mountpoints etc. Hence a utility to do it.

What File::Find will let you do is specify a chunk of code to run on e.g. each file if find.

As an example -

sub do_something {
   print $File::Find::name,"\n";
}

find ( \&do_something, "." );  

That aside - there's several things in your code that seems to suggest you're going off in an odd direction.

  • counting elements in an array is much simpler by testing the array in a scalar context. Instead of:

    for ( @array ) { $elementcount++; }

You can just do:

my $elementcount = @array; 
  • splitting STDIN on spaces is a bit risky, because it assumes you have no filename with spaces in. But lets stick with it for now.

  • NEVER assign values to @_. This is a built in variable, and messing with it can break things in strange and horrible ways.

  • Why have you encapsulated the 'getting input' into a subroutine?

Anyway - assuming you need to traverse the filesystem to find your directory/files:

use strict;
use warnings;
use File::Find;

sub find_matches {
    my ( $search, $results_ref ) = @_;
    if ( $_ eq $search ) {
        push( @$results_ref, $File::Find::name );
    }
}

while (<STDIN>) {
    print "Please enter directory or file name:\n";
    my @parameters = split(/\s+/); #split input in one or more whitespace.

    #won't work if filename includes whitespace!

    unless ( scalar @parameters == 1 ) {
        print "Please enter only one value\n";
        next;
    }
    my $search_text = pop @parameters;
    my @results;
    find( sub { \&find_matches( $search_text, \@results ) },
        "/path/to/search" );

    #now results has a list of all full paths to your files.
    #because you traversed, you may well have  duplicates.

    if ( not @results ) {
        print "No matches found\n";
    }
    foreach my $full_file_path (@results) {
        if ( -d $full_file_path ) {
            print "$full_file_path is a directory\n";

            # do directory stuff;
            last;
        }
        if ( -z $full_file_path ) {
            print "$full_file_path is zero length\n";

        }
    }
    last; #breaks you out the while loop - the 'next' above will skip this line
          #if you don't have a valid parameter. 
}

Hopefully that should get you started? What's happening is - you're using File::Find to give you a list of matches for a text string, that you can then go through and process individually. Bear in mind that your assignment spec is unclear on what you should do if you have a directory and a file that matches, which is why I'm not convinced you need to do the traversal.

Sobrique
  • 52,974
  • 7
  • 60
  • 101