Skip to content
VLSI Mentor

Perl Part I · Module 11 · Regular Expressions

/x Mode: Patterns a Human Can Read

A pattern that matches five fields is about sixty characters of punctuation on one line, and nobody can review it. That is not a stylistic complaint — it is the practical reason real parsing is so often done with split and a pile of index arithmetic instead, which is harder to get right and no easier to read. The /x modifier removes the excuse. It tells the engine to ignore whitespace in the pattern and to treat a # as the start of a comment, which means a long pattern can be laid out one component per line, each with a note saying which field it matches. The pattern becomes the format specification, reviewable by someone who does not write Perl. The cost is that two characters you were using as data are now syntax, and section three measures what that does to a pattern that depended on them: not a clean failure, but a pattern that quietly stops telling two different inputs apart.

Intermediate11 min readPerlPart IRegular Expressions

Part I · Module 11 · Chapter 11.17 · /x Mode: Patterns a Human Can Read

1. The Same Pattern, Twice

Azvya Education Pvt. Ltd.VLSI Mentor
unreadable.pl
use strict;
use warnings;

my $row = "ERROR u_core/u_alu/A setup -0.12 ns";

# The same rule written as one dense line
my $dense = qr{^(\w+)\s+(\S+)\s+(\w+)\s+(-?\d+\.\d+)\s+(\w+)$};

# ...and laid out with /x
my $clear = qr{
    ^
    (\w+)            # severity
    \s+
    (\S+)            # pin, may contain slashes
    \s+
    (\w+)            # check type
    \s+
    (-?\d+\.\d+)     # slack, signed decimal
    \s+
    (\w+)            # unit
    $
}x;

my @a = $row =~ $dense;
my @b = $row =~ $clear;

printf "dense captures : %d  %s\n", scalar @a, join(" | ", @a);
printf "/x    captures : %d  %s\n", scalar @b, join(" | ", @b);
printf "identical      : %s\n", ("@a" eq "@b" ? "yes" : "no");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
dense captures : 5  ERROR | u_core/u_alu/A | setup | -0.12 | ns
/x    captures : 5  ERROR | u_core/u_alu/A | setup | -0.12 | ns
identical      : yes

Five captures each, and the results are identical. The two patterns are the same pattern; one of them can be reviewed.

That equivalence is the point. Laying a pattern out costs nothing at run time — the whitespace and comments are discarded when the pattern is compiled, not scanned on every match — so there is no reason to prefer the dense form for anything beyond about three components.

Notice what the laid-out version gains beyond line breaks: each capture group has a name in a comment, which is the field it matches. Chapter 11.11's named captures do this inside the pattern and are better still; the comments here work even on a pattern whose captures you are reading positionally, and they survive someone adding a group at the front — the hazard Chapter 11.9 measured.

The qr{...} wrapper is Chapter 11.18's subject. It appears here because storing a laid-out pattern in a variable is how you actually use one — you build it once, near the comment explaining the format, and apply it in the loop.

2. Whitespace Is Now Syntax

The cost side, measured.

Azvya Education Pvt. Ltd.VLSI Mentor
space-is-ignored.pl
use strict;
use warnings;

my $subject = "a b";

# Under /x a literal space in the pattern is IGNORED
printf "/a b/   on 'a b' : %s\n", ($subject =~ /a b/   ? "match" : "no match");
printf "/a b/x  on 'a b' : %s\n", ($subject =~ /a b/x  ? "match" : "no match");
printf "/a\\ b/x on 'a b' : %s\n", ($subject =~ /a\ b/x ? "match" : "no match");
printf "/a[ ]b/x        : %s\n", ($subject =~ /a[ ]b/x ? "match" : "no match");
printf "/a\\sb/x         : %s\n", ($subject =~ /a\sb/x  ? "match" : "no match");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
/a b/   on 'a b' : match
/a b/x  on 'a b' : no match
/a\ b/x on 'a b' : match
/a[ ]b/x        : match
/a\sb/x         : match

The plain pattern matched. Adding /x made it stop matching, because the space between a and b was discarded and the pattern became /ab/.

Three ways to write a space that survives, all shown matching: escape it as \ , put it in a class as [ ], or — much the best — use \s or \s+, which is what you almost always meant anyway. Chapter 11.5 argued for \s+ between fields on the grounds that it survives a tool changing its column alignment; here is a second reason, which is that it survives you adding /x.

3. And a # Takes the Rest of the Line

The second repurposed character fails much less politely.

Azvya Education Pvt. Ltd.VLSI Mentor
hash-is-a-comment.pl
use strict;
use warnings;

# Under /x, a # starts a comment that runs to the end of the line.
# Anchored, so the difference is visible rather than accidental.
printf "%-22s %-10s %s\n", "PATTERN", "on 'a#b'", "on 'a'";

printf "%-22s %-10s %s\n", "/^a#b\$/",
    ("a#b" =~ /^a#b$/   ? "match" : "-"),
    ("a"   =~ /^a#b$/   ? "match" : "-");

printf "%-22s %-10s %s\n", "/^a#b\$/x",
    ("a#b" =~ /^a#b$/x  ? "match" : "-"),
    ("a"   =~ /^a#b$/x  ? "match" : "-");

printf "%-22s %-10s %s\n", "/^a\\#b\$/x",
    ("a#b" =~ /^a\#b$/x ? "match" : "-"),
    ("a"   =~ /^a\#b$/x ? "match" : "-");

printf "%-22s %-10s %s\n", "/^a[#]b\$/x",
    ("a#b" =~ /^a[#]b$/x ? "match" : "-"),
    ("a"   =~ /^a[#]b$/x ? "match" : "-");

print "\nunder /x the bare # swallowed 'b\$', leaving just /^a/\n";
print "that matches BOTH subjects, so the pattern stopped discriminating\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
PATTERN                on 'a#b'   on 'a'
/^a#b$/                match      -
/^a#b$/x               match      match
/^a\#b$/x              match      -
/^a[#]b$/x             match      -

under /x the bare # swallowed 'b$', leaving just /^a/
that matches BOTH subjects, so the pattern stopped discriminating

Read the second row against the first. The plain pattern told the two subjects apart: a#b matched, a did not. The /x version matched both.

That is a worse outcome than the space case. The # did not just remove a character — it commented out everything after it, including the closing $ anchor, so the whole pattern degenerated to /^a/. It still matches the input you tested with. It has simply stopped rejecting anything, and a pattern that accepts everything is the failure mode Chapter 11.6 warned about with /^.*$/.

The two fixes work as they did for the space: escape it as \#, or put it in a class as [#]. The class form is easier to spot in a laid-out pattern.

This matters in this field specifically, because # is a comment character in Tcl, in SDC constraint files, in Makefiles and in shell. Any pattern that matches the comment marker of a file format is a pattern with a # in it, and adding /x to it will break it in exactly this silent way.

4. Whitespace Inside a Character Class

One boundary that surprises people who assume /x applies everywhere.

Azvya Education Pvt. Ltd.VLSI Mentor
class-whitespace.pl
use strict;
use warnings;

# Under plain /x, whitespace INSIDE a character class is still significant
my $sp = " ";
printf "' ' =~ /[ab ]/x  : %s   <- the space in the class still counts\n",
    ($sp =~ /^[ab ]$/x ? "match" : "no match");

# /xx additionally ignores whitespace inside classes (Perl 5.26+)
printf "' ' =~ /[ab ]/xx : %s   <- /xx ignores it\n",
    ($sp =~ /^[ab ]$/xx ? "match" : "no match");
printf "perl version     : %vd\n", $^V;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
' ' =~ /[ab ]/x  : match   <- the space in the class still counts
' ' =~ /[ab ]/xx : no match   <- /xx ignores it
perl version     : 5.34.1

Under plain /x the space inside [ab ] is still a member of the class, so a space matched. This is a sensible exemption: it means [ ] remains a reliable way to write a literal space, as section two used.

Doubling the modifier to /xx extends the whitespace-stripping into character classes, so [ab ] becomes [ab] and the space no longer matches. That is occasionally useful for laying out a long class — [ a-z A-Z 0-9 _ ] reads better than the run-together version — and it is available from Perl 5.26, which the version line confirms this machine comfortably exceeds.

The practical advice is to use plain /x and keep [ ] meaning a space. Reach for /xx only when a class is long enough to need internal spacing, and then remember that [ ] inside it no longer works.

5. When To Use It

A short rule, since this modifier is about judgement rather than behaviour.

Use /x when the pattern has more than about three components, or when any reader would have to count characters to see where one part ends and the next begins. A five-field row matcher, a date format, a hierarchical name with optional segments: all of them are clearer laid out.

Do not use it for short patterns. /^\d+$/x is not improved by the modifier; it is just a pattern with an unnecessary flag that invites someone to add a literal space later.

And when you do lay a pattern out, put the comments to work. A component with no comment is no better off than it was on one line — the line break alone does not explain anything. The comment that earns its place is the one naming the field, because that is the knowledge the pattern otherwise fails to carry.

6. What To Remember

  • /x ignores whitespace in the pattern and treats # as a comment, so a long pattern can be laid out one component per line and annotated.
  • It costs nothing at run time. The dense and laid-out patterns produced identical captures; whitespace is discarded at compile time.
  • A literal space stops working. /a b/x is /ab/. Write \s+ (best), \ , or [ ].
  • Convert spaces to \s+ before adding /x, not after.
  • A bare # comments out the rest of the pattern line, which can swallow an anchor. Measured: /^a#b$/x became /^a/ and matched both subjects — it stopped discriminating rather than stopped matching.
  • That matters here specifically, because # is the comment marker in Tcl, SDC, Makefiles and shell — so patterns that match comment lines are exactly the ones /x breaks.
  • Plain /x does not reach inside [ ], which is why [ ] is a reliable literal space. /xx does reach inside, from Perl 5.26.
  • Use it past about three components, and make the comments name the fields.

Chapter 11.18 takes the qr{...} wrapper this chapter kept using and makes it the subject: a pattern stored in a variable, built once and reused, interpolated into larger patterns, and kept in a hash so that one script can recognise several input formats.

Where this fits

Part of the Perl curriculum.