Perl regex Unicode properties
last modified October 20, 2026
Perl regular expressions work on Unicode text and understand character properties. Instead of listing ranges by hand, a pattern can ask for letters, numbers, a particular script, or a whole user-perceived character. This article shows the most useful properties for text processing.
Properties for letters, numbers and symbols
The escape \p{...} matches a character that has a Unicode property,
and \P{...} matches one that does not. General categories include
L for letters, Lu and Ll for upper and
lower case, N for numbers and Sc for currency symbols.
The script needs use utf8 to read the source as UTF-8 and an output
layer to print it.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use utf8;
use open qw(:std :encoding(UTF-8));
my $text = 'Zoë paid 5 € for naïve café crème, Ünal said 三 times.';
my @words = $text =~ /\p{L}+/g;
say "letter runs: @words";
my @ascii = $text =~ /[A-Za-z]+/g;
say "ASCII only: @ascii";
my @upper = $text =~ /\p{Lu}/g;
say "uppercase: @upper";
my @numbers = $text =~ /\p{N}+/g;
say "numbers: @numbers";
my @currency = $text =~ /\p{Sc}/g;
say "currency: @currency";
say "non-letters: ", scalar(() = $text =~ /\P{L}/g);
The pattern \p{L}+ finds whole words including accented and Chinese
letters, while the ASCII range breaks Zoë into pieces and misses
the Chinese character. The other properties extract capital letters, numbers and
the euro sign. \P{L} counts everything that is not a letter,
including spaces and punctuation.
$ perl letters.pl letter runs: Zoë paid for naïve café crème Ünal said 三 times ASCII only: Zo paid for na ve caf cr me nal said times uppercase: Z Ü numbers: 5 currency: € non-letters: 15
Scripts
Properties also exist for scripts, such as \p{Latin},
\p{Cyrillic}, \p{Greek}, \p{Han},
\p{Hiragana} and \p{Hebrew}. They make it possible to
detect which writing systems a string uses.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use utf8;
use open qw(:std :encoding(UTF-8));
my @samples = ('Hello', 'Привет', 'γειά σου', '你好', 'こんにちは', 'שלום', 'mix Привет');
for my $s (@samples) {
my @found;
push @found, 'Latin' if $s =~ /\p{Latin}/;
push @found, 'Cyrillic' if $s =~ /\p{Cyrillic}/;
push @found, 'Greek' if $s =~ /\p{Greek}/;
push @found, 'Han' if $s =~ /\p{Han}/;
push @found, 'Hiragana' if $s =~ /\p{Hiragana}/;
push @found, 'Hebrew' if $s =~ /\p{Hebrew}/;
say "$s: ", join ', ', @found;
}
Each sample is checked against several script properties. The text that mixes Latin and Cyrillic reports both, which is a typical test for spoofed identifiers where letters from different scripts look alike. The Japanese word is Hiragana only, while the Chinese one is Han.
$ perl scripts.pl Hello: Latin Привет: Cyrillic γειά σου: Greek 你好: Han こんにちは: Hiragana שלום: Hebrew mix Привет: Latin, Cyrillic
Grapheme clusters with \X
A character that the user sees may consist of several code points: a letter
followed by combining accents, or a flag made of two regional indicators. The
escape \X matches one such extended grapheme cluster, and
\p{M} matches a combining mark.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use utf8;
use open qw(:std :encoding(UTF-8));
my $cafe = "cafe\x{301}";
my $flag = "\x{1F1F8}\x{1F1F0}";
my $text = "$cafe $flag";
say "text: $text";
say "code points: ", length $text;
say "graphemes: ", scalar(() = $text =~ /\X/g);
my @clusters = $cafe =~ /\X/g;
say "clusters of cafe: ", scalar @clusters;
say "dot matches: ", scalar(() = $cafe =~ /./g);
say "combining marks: ", scalar(() = $cafe =~ /\p{M}/g);
The string has eight code points but only six visible characters. The letter
é is written here as e followed by U+0301, so
. sees it as two characters, while \X keeps them
together. The flag is also one cluster. Use \X whenever text is
counted or cut for display.
$ perl grapheme.pl text: café 🇸🇰 code points: 8 graphemes: 6 clusters of cafe: 4 dot matches: 5 combining marks: 1
Case-insensitive matching
With Unicode strings, case-insensitive matching uses full case folding, so it
follows rules beyond simple lower casing. The function fc returns
the folded form for comparisons.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use utf8;
use open qw(:std :encoding(UTF-8));
my @words = ('Straße', 'STRASSE', 'strasse', 'ÉCOLE', 'école');
for my $w (@words) {
my @hits;
push @hits, 'STRASSE' if $w =~ /^strasse$/i;
push @hits, 'ÉCOLE' if $w =~ /^école$/i;
say "$w matches: ", @hits ? "@hits" : 'nothing';
}
say "fc equal: ", (fc('Straße') eq fc('STRASSE') ? 'yes' : 'no');
say "lc equal: ", (lc('Straße') eq lc('STRASSE') ? 'yes' : 'no');
my $title = 'élan vital';
$title =~ s/(\p{L}+)/\u$1/g;
say $title;
The pattern strasse with /i matches the German word
with ß, since folding turns it into ss. The accented
capital in ÉCOLE matches the lower case spelling as well. The
comparison of lc results fails for ß, but
fc succeeds. The \u escape in the substitution
capitalizes the accented first letter.
$ perl case.pl Straße matches: STRASSE STRASSE matches: STRASSE strasse matches: STRASSE ÉCOLE matches: ÉCOLE école matches: ÉCOLE fc equal: yes lc equal: no Élan Vital
Word boundaries and scripts without spaces
The ordinary \b treats an apostrophe or a dot as a boundary. The
Unicode word boundary \b{wb} follows the rules of Unicode Standard
Annex 29, and handles contractions and decimal numbers. Languages written
without spaces need properties instead.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
use utf8;
use open qw(:std :encoding(UTF-8));
my $s = "Don't stop, it's 3.14!";
my @bw = $s =~ /\b\w+\b/g;
say "\\b\\w+\\b: ", join '|', @bw;
my @wb = $s =~ /\b{wb}(?=\w)[\w'.]+\b{wb}/g;
say "\\b{wb}: ", join '|', @wb;
my $japanese = '日本語のテキスト';
say "\\w+ on kanji run: ", join '|', $japanese =~ /\w+/g;
say "\\p{Han}+: ", join '|', $japanese =~ /\p{Han}+/g;
say "\\p{Katakana}+: ", join '|', $japanese =~ /\p{Katakana}+/g;
The plain \b\w+\b splits Don't and 3.14
into pieces. The \b{wb} version, combined with a character class
that admits the apostrophe and the dot, keeps them whole. For the Japanese text,
\w+ returns one long run because there are no spaces, but the
script properties separate the kanji from the katakana.
$ perl boundary.pl
\b\w+\b: Don|t|stop|it|s|3|14
\b{wb}: Don't|stop|it's|3.14
\w+ on kanji run: 日本語のテキスト
\p{Han}+: 日本語
\p{Katakana}+: テキスト
Best Practices
- Decode input first: match on character strings, not on bytes; use
use utf8and:encoding(UTF-8)layers. - Use \p{L} instead of [A-Za-z]: ranges miss every non-English letter.
- Use \X for display-oriented work: count and cut graphemes, not code points.
- Compare with fc: do not rely on
lcfor case-insensitive equality. - Normalize when needed: composed and decomposed forms of the same text do not match;
Unicode::Normalizefixes that.
In this article we have covered Unicode in Perl regular expressions: general
categories, scripts, grapheme clusters with \X, case folding, and
word boundaries.
Source
perluniprops: Unicode Character Properties
Author
List all Perl tutorials.