

Welcome to our third installment of a four part series exploring the Perl language. We’ll now focus on regular expressions, which are powerful tools and one of Perl’s signature features.

For your reference, here is a copy of our Hello World test program we’ve used since our first installment (Part 1).

#!/usr/bin/perl -wuse strict;# Process command-line parametersdie "Usage:n filename.txtn"     # Print a usage message...  unless @ARGV == 1;                            # ...if the number of arguments is not correctmy $filename = $ARGV[0];# Say hi!open(OUTPUT_FILE, ">$filename") or die "Couldn't open file: $!";print OUTPUT_FILE "Hello, world!n";close(OUTPUT_FILE);# We're doneexit(0);

Intro to Regular Expressions
In Part II of our tutorial, I mentioned that we used a regular expressions to split a filepath into its constituent parts, without going into any detail as to how we accomplished that feat.

As it turns out, regular expressions (a.k.a. regexps, regexes, or occasionally REs) are one of Perl’s most powerful features. If you do any work that involves serious text manipulation, becoming proficient with regular expressions will massively magnify your productivity. And regular expressions are useful even outside of the Perl world: Many text editors provide at least limited support for matching text with regular expressions, as do powerful Unix command-line tools such as grep.

Think of a regular expression as a template that can match text meeting certain attributes. Conceptually, regexps are like a vastly more powerful version of the DOS filename wildcards ? and * that you’re probably used to using on a command line or in a file dialog box.

In its simplest form, a regex just matches a particular sequence of defined characters. Let’s say, for instance, we add a snippet of code like this to our file-counting program:

foreach my $this_file (@all_files) {
print $this_file if $this_file =~ m/readme/;

The =~ operator means roughly “take the thing to my left (in this case, the contents of the variable $this_file) and perform the pattern-matching operation specified on the right, then return a value that we can store or test. The m to the right of =~ indicates that we want to perform a match, and the slashes delimit the regex we intend to match against.

The regexp shown in this example, /readme/, will simply match any string that contains the sequence of characters “readme” without interruption. So a filename like readme.txt would of course match, as would please-readme.html, but read-this-first.txt would not match. Neither would README.TXT, because by default regular-expression matching is case-sensitive.

As this example illustrates, alphanumeric characters in regular expressions just match themselves. Where things get interesting is when you start using special metacharacters in your regular expressions. For example, the pipe symbol (|) means “or”:

print $this_file if $this_file =~ m/readme|info|instructions/;

This regex will match any file containing the character sequence “readme”, or the sequence “info”, or the sequence “instructions”.

You can insert wildcards into your regular expression, too:

print $this_file if $this_file =~ m/;

The dot (.) means “match any single character”. So this regular expression would match, among other things, readme.txt and realme.doc.

If matching any character is too general, you can be more specific about which characters are allowed in a given position by listing allowable characters between brackets. For instance:

print $this_file if $this_file =~ m/readme[123]/;

prints only files containing the string readme1, readme2, or readme3. Or, in a more compact form:

print $this_file if $this_file =~ m/readme[1-3]/;

To represent multiple ranges of characters between brackets, you can just string them together back to back:

print $this_file if $this_file =~ m/readme[1-3a-cA-C]/;

This would match any filename containing the letters readme followed by either a 1, 2, or 3, or an upper or lower case A, B, or C. Notice that a-c and A-C are listed separately because matching is case-sensitive by default. If you want to use case-insensitive matching, you can add an i suffix to the pattern match:

# matches either lower case a-c or upper case A-C
print $this_file if $this_file =~ m/readme[1-3a-c]/i;

Now for the really fun part. You can qualify any preceding character or group of characters by using the ?, +, or * suffixes, which have the following significance:

? optional
+ occurs one or more times
* occurs zero or more times

Here’s an example:

print $this_file if $this_file =~ m/readme[1-3]?now/;

The ? means that the preceding [1-3] is optional, so this regular expression would match readmenow (with no 1, 2, or 3) as well as readme1now, readme2now, or readme3now.

print $this_file if $this_file =~ m/readme[0-9]+now/;

would match readme2now, readme94now, readme782671now, or any other name that includes one or more digits between read and now. It would not match readmenow with no intervening digits at all, however, because a + modifier requires at least one instance of the character it’s modifying. You could instead use /readme[0-9]*now/ if having no digits is acceptable.

What if you actually need to match against one of these characters, like a dot, that has a special meaning in regex-land? Just preface it with a backslash. For example, this regex will match any file whose name contains .txt:

print $this_file if $this_file =~ m/.txt/i;