2008 Winter Scripting Games

Solution to Beginner Perl Event 3: Let’s Get Together

Event 3 Solution


Perl solution to Event 3 in the 2008 Winter Scripting Games.

Solutions are also available for VBScript and Windows PowerShell.

*

Event 3 – Let’s Get Together

This event was all about finding files within folders, reading from those files, and even creating new files. Whew, that’s a lot. Let’s get started and see how we solved this one:

$search_dir = "C:\\scripts\\";
$which_files = ".txt";

opendir DIR, $search_dir;
@files = grep(/$which_files/, readdir(DIR));
closedir DIR;

open (TEXT, '>C:\\Temp\\newtext.txt');

for $file (@files)
{
    $file = $search_dir . $file;
    open FH, $file;
    @lines = (<FH>);
    print TEXT @lines[0];
    close FH;
} 
close TEXT;

As with most things in Perl, there are many different ways to accomplish these tasks. We’ll start out showing you one solution, then at the end, just because we actually managed to find a second solution, we’ll show you that too. But first this one.

We start by setting a variable to the folder that contains all the text files we want to search:

$search_dir = "C:\\Scripts\\";

Notice that we have two backslashes in the path; that’s because in Perl the backslash character has a special meaning on its own, so we need to make it clear that we really want a backslash here and not a special character. We do that by “escaping” the backslash, meaning we double it up and use two backslashes instead of one.

Next we define another variable, this one containing the files we want to look for:

$which_files = ".txt";

We’ll use this variable to indicate that we want to find all the files with .txt in their names. Notice that we didn’t use a wildcard character; we’ll explain why when we show you how we’ve used this variable. But first we need to open the folder we want to read:

opendir DIR, $search_dir;

We’ve used the opendir function to open the folder. We pass opendir two parameters: DIR, the variable where we’ll keep a reference to the folder; and $search_dir, the folder we want to open.

Now that we have our folder open we can read the files from that folder:

@files = grep(/$which_files/, readdir(DIR));

To read from the folder we’ve used the grep function. The grep function is pretty powerful; it lets you do some pretty complicated searches once you know how to use it. In this case we’re keeping it pretty simple. We pass grep two parameters:

/$which_files/ The files we want to find in the folder. We’ve used forward slashes to indicate that we’re searching for anything that matches the pattern within those slashes, in this case “.txt”, the contents of our search variable.

readdir(DIR) The readdir function reads the contents of the folder referenced by DIR.

So what’s really happening here is we’re reading the files from the folder we just opened (DIR) and filtering the results on $which_files, or “.txt”. The result will be a list of all the text files in the C:\Scripts folder, which we’ve put into the array @files.

At this point we don’t need the folder open anymore, so we go ahead and call the closedir method to close it:

closedir DIR;

Next we want to create the new file we’ll be writing to. We do that by calling the open method:

open (TEXT, '>C:\\Temp\\newtext.txt');

We pass open two parameters:

TEXT. The open method will place a reference to the new file in this variable.

'>C:\\Temp\\newtext.txt' .The full path and filename of the new file. Notice two things here. First, we once again needed to escape the backslashes and double them up. Second, take a look at that first character, which looks like the greater than sign (>). That indicates that we’ll be writing to this file.

Now it’s time to start looping through our list of files. We do that with a foreach loop:

for $file (@files)

You’re right, that doesn’t look like a foreach loop;, it looks like a for loop. Well, in Perl you can use for in this manner as shorthand for foreach. This would work too:

foreach $file (@files)

Let’s go back for a moment. When we read our list of files into the array @files, we didn’t get back the full path to the files, we got back only the filename. However, in order to open those files we need the full path. So the first thing we do inside our loop is to reconstruct the path to the file:

$file = $search_dir . $file;

As you can see, we take the path to our files (located in the $search_dir variable) and then use the dot (.) to concatenate that path with the filename ($file). We then assign this full path and filename back to the $file variable. Now we can open the file:

open FH, $file;

Just like we did when we opened the new file we’re going to write to, we use the open method to open this existing file, passing it the variable (FH) where open will place the reference to the file and the filename ($file). Notice we left off the >, we won’t be writing to this file, only reading from it.

Speaking of reading from the file, that’s what we do next:

@lines = (<FH>);

It’s pretty easy to read the contents of a file. We simply place the reference to the file inside <> which we then place inside (), and assign it to a variable (@lines). Why <> and ()? Well, that’s just the way Perl works. It might seem a bit cryptic, but after running this line of code each element in the array @lines will contain one line from the file referenced by FH, the current file in our list.

Now we need to retrieve the first line in our file and add it to the new file. We can do this all in one step by calling the print method:

print TEXT @lines[0];

We pass print two parameters:

TEXT. The reference to our new file, which is the file we want to write to.

@lines[0]. The first element in our array that contains our file contents. The first element of the array just happens to hold the first line of the file.

After we’ve written the line to the file we can close that file and loop around and do the same thing with the next text file:

close FH;

When we’ve looped through all the files we simply close the new file we’ve been writing to:

close TEXT;

We now have a new file, C:\Temp\newtext.txt, that contains the first line from every text file in C:\Script. And that just happens to be exactly what we needed to successfully complete this event.

Oh yes, we said we’d show you the other solution we came up with. Here it is:

$search_dir = "C:\\scripts\\";

chdir $search_dir;
@files = glob '*.txt';

open (TEXT, '>C:\\temp\\newtext.txt');

for $file (@files)
{
    $file = $search_dir . $file;
    open FH, $file;
    @lines = (<FH>);
    print TEXT @lines[0];
    close FH;
} 
close TEXT;

This solution is almost identical to the first one we showed you. We start by specifying the folder we want to read from and assigning that to the $search_dir variable. This time, however, we simply change directories to that folder:

chdir $search_dir;

Now that our script is pointing to the folder we want to search, we can call the glob function to find all the .txt files:

@files = glob '*.txt';

We pass one parameter to the glob function, *.txt. Notice that this time we need a wildcard character (*) in our function call. What happens here is that the glob function will get a list of all files in the current directory (which is why we changed directories) that end in .txt; in other words, all the text files. We assign that list of files to the array @files.

From there the rest of the script is identical. So whichever way you decided to complete the event, whether it was one of these solutions or a completely different one, as long as your script successfully did what was outlined in the event instructions without any errors you received points for this event.


Top of pageTop of page