\w is ASCII, [[:alpha:]] is not, and \b sides with Unicode¶
Level: 201 · anyone matching words in text that is not all English
One line: On UTF-8 text Ruby's \w, \d and \s match ASCII characters only, while [[:alpha:]], [[:digit:]], [[:space:]] and \p{L} match Unicode — and \b uses the Unicode idea of a word, so /\b\w+\b/ cannot match café at all.
Verified output of w_is_ascii_b_is_not_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
word = "zażółć"
word.scan(/\w+/) ["za"]
word.scan(/[[:alpha:]]+/) ["zażółć"]
word.scan(/\p{L}+/) ["zażółć"]
word.scan(/(?u)\w+/) ["zażółć"]
"caf\u{e9} au lait".scan(/\b\w+\b/) ["au", "lait"]
"caf\u{e9} au lait".scan(/\w+/) ["caf", "au", "lait"]
"caf\u{e9}" =~ /caf\b/ nil
"\u{661}\u{662}\u{663}" =~ /\d/ nil
"\u{661}\u{662}\u{663}" =~ /[[:digit:]]/ 0
"a\u{a0}b" =~ /\s/ nil
"a\u{a0}b" =~ /[[:space:]]/ 1
Reading the output¶
\w+stops atż. The POSIX bracket,\p{L}and(?u)\wall take the whole word.\band\wdisagree abouté. To\b,éis a word character, so there is no boundary betweenfandé. To\w,éis not a word character, so\w+cannot reach pastcaf. Between them,/\b\w+\b/findsauandlaitand skipscaféentirely, and/caf\b/does not match insidecafé.- Arabic-Indic digits are
[[:digit:]]but not\d— andInteger()does not read them either, seeto_inever fails. - A no-break space is
[[:space:]]but not\s— and awk-modesplitdoes not split on it, seesplithas an awk mode.
What to do¶
In a pattern that may meet text beyond ASCII, write the POSIX brackets or \p{...} — [[:alpha:]], [[:alnum:]], \p{L} — or put (?u) in front. Keep \d where you mean exactly the digits 0 to 9, which is usually what a parser wants.
If you are coming from another language¶
Perl treats text as Unicode here — given use feature 'unicode_strings', for the reasons in the Perl library's The Unicode bug ↗ — and /a restricts \w, \d and \s to ASCII:
Verified output of w_is_ascii_b_is_not_pl.pl — regenerated by tools/run_examples.py, never hand-typed.
$word =~ /\w+/g zażółć
$word =~ /\w+/ag za
$cafe =~ /\b\w+\b/g café au lait
"\x{661}\x{662}\x{663}" =~ /\d/ match
"a\x{A0}b" =~ /\s/ match
Python's re is Unicode by default for a str pattern, and re.ASCII turns that off:
Verified output of w_is_ascii_b_is_not_py.py — regenerated by tools/run_examples.py, never hand-typed.
re.findall(r'\w+', word) ['zażółć']
re.findall(r'\w+', word, re.ASCII) ['za']
re.findall(r'\b\w+\b', cafe) ['café', 'au', 'lait']
re.search(r'\d', digits) True
re.search(r'\s', 'a\xa0b') True
So Ruby's defaults are the opposite of both, and Ruby is the one of the three where \b and \w disagree. Java makes all four of \w, \d, \s and \b ASCII by default, consistently — the Java text library's \w is ASCII by default ↗. The Encodings library's "Supports Unicode" is a level, not a yes ↗ grades regex engines against Unicode's own checklist.