ZetCode

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.

letters.pl
#!/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.

scripts.pl
#!/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.

grapheme.pl
#!/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.

case.pl
#!/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.

boundary.pl
#!/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

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.