Regular Expressions and Patterns
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Written against MATLAB R2026b. The regular-expression sessions below were run in GNU Octave 11.3.0 (its regular-expression engine accepts the MATLAB syntax used here) and show Octave’s output, with character vectors in single quotes. Octave 11.3.0 does not have pattern objects, string arrays or dynamic expressions, so those parts are "not run here": they follow the R2026b documentation, and the results quoted for them are what its rules give. The pattern functions were introduced in R2020b.
MATLAB has two ways to describe text to search for. A regular expression is a compact string in a separate mini-language, used by regexp, regexpi and regexprep. A pattern (since R2020b) is a MATLAB object built from functions, the operators +, | and ~, and literal text, used by contains, extract, replace, matches and related functions. The documentation presents patterns as the simpler alternative that gives code that is easier to read; regular expressions remain the more powerful tool and are what older code and the books use. For plain, literal searches strfind, contains and replace are enough (see Strings and Character Arrays).
The regular-expression functions
-
regexp(str,expression)finds the matches ofexpressioninstr;regexpiis the same but ignores letter case.regexprep(str,expression,replace)replaces the matches.regexptranslate(op,txt)converts text into a regular expression (for example"escape", which backslash-escapes special characters, and"wildcard", which turns a file-style wildcard such as*.minto a regular expression). -
The first two inputs of the three search functions are the text and the expression. Both can be a character vector, a string, or a cell or string array of them; if both are arrays they must have the same size.
-
MATLAB scans the text from left to right and "consumes" it: after a match the search resumes just after the end of that match, so matches never overlap.
regexp returns a different output depending on the output keyword you ask for, in any order and number ([tok,mat] = regexp(str,expr,"tokens","match")):
| Keyword | Output |
|---|---|
|
the starting and ending indices of the matches (this is what |
|
the start and end indices of each token |
|
the text of each match |
|
the text of each token (the parts in parentheses) of each match |
|
a structure with the name and text of each named token |
|
the pieces of text between the matches |
With no keyword and several outputs the order is start, end, token extents, match, tokens, names, split. When there is no match, the numeric outputs and "match" are empty. The type of the output follows the input: for a character vector "match" and "tokens" are cells of character vectors; for a string the match output is a string array; for cell or string array inputs the outputs are cell arrays.
The options come in pairs, a default and its override, and may be given in any order after the outputs:
| Option | Effect |
|---|---|
|
return only the first match, as plain text instead of a cell |
|
ignore letter case ( |
|
keep zero-length matches, which are ignored by default |
|
make |
|
make |
|
display warnings (suppressed by default) |
|
return each output as a scalar cell |
The reference page lists a few more option pairs (for example one for free-spacing expressions). "once" changes the output type: a match comes back as text, and a failed search comes back as empty text, so isempty(...) is the natural test for "did it match".
str = 'bat cat can car coat court CUT ct CAT-scan';
startIndex = regexp(str, 'c[aeiou]+t')
[s, e] = regexp(str, 'c[aeiou]+t')
m = regexp(str, 'c[aeiou]+t', 'match')
mi = regexpi(str, 'c[aeiou]+t', 'match')
m1 = regexp(str, 'c[aeiou]+t', 'match', 'once')
nomatch = regexp(str, 'xyz', 'match')
none = regexp(str, 'xyz', 'match', 'once')
parts = regexp('a,b;;c', '[,;]', 'split')
[parts2, delims] = regexp('a,b;;c', '[,;]', 'split', 'match');
delims
Octave 11.3.0 output (the cell layout differs in MATLAB, which shows {'cat'} {'coat'} on one line):
startIndex =
5 17
s =
5 17
e =
7 20
m =
{
[1,1] = cat
[1,2] = coat
}
mi =
{
[1,1] = cat
[1,2] = coat
[1,3] = CUT
[1,4] = CAT
}
m1 = cat
nomatch = {}(1x0)
none =
parts =
{
[1,1] = a
[1,2] = b
[1,3] =
[1,4] = c
}
delims =
{
[1,1] = ,
[1,2] = ;
[1,3] = ;
}
The example is the one of the regexp reference page (c[aeiou]+t, which finds the words with a c, one or more vowels and a t; CUT and CAT need regexpi). "split" keeps the empty piece between the two adjacent delimiters, which strsplit with its default collapsing of delimiters would drop.
Source: MATLAB documentation: regexp
Regular-expression syntax
The tables of the documentation group the elements of a regular expression. In a character vector the backslashes are literal characters passed to regexp, which interprets them, so '\d+' needs no doubling.
Metacharacters and character classes
| Element | Matches |
|---|---|
|
any single character, including white space |
|
any character in the set, or any character not in the set (the characters |
|
any character in the range |
|
a word character (letter, digit, underscore), or any other character |
|
a white-space character, or any other |
|
a digit, or a non-digit |
|
the character with hexadecimal or octal code N |
|
the control characters; |
Quantifiers
| Element | Number of times |
|---|---|
|
0 or more, 1 or more, 0 or 1 |
|
exactly n, between m and n, at least m |
Each quantifier has three modes. Greedy (expr+) matches as much as possible; lazy (add a ?: expr+?) as little as necessary; possessive (add a +: expr++) takes as much as possible and never gives characters back. The documentation’s example is the text <tr><td><p>text</p></td>: the greedy </?t.*> matches the whole text from <tr to /td>, the lazy </?t.*?> returns the three tags <tr>, <td> and </td>, and the possessive </?t.*+> finds no match because .*+ consumes the closing >. Prefer the lazy form or a negated class (<[^>]*>) when you mean "up to the first closing bracket".
Grouping, alternation and anchors
| Element | Meaning |
|---|---|
|
group, and capture a token |
|
group without capturing |
|
atomic group: no backtracking inside, and no token |
|
alternation: expr1, or expr2 (the first that matches wins) |
|
the start and the end of the text (of each line with |
|
the start and the end of a word |
Lookaround, tokens and conditions
| Element | Meaning |
|---|---|
|
|
|
|
|
capture a token, and match the same text again ( |
|
capture a named token, and match its text again |
|
if the condition (a token, a lookaround or a dynamic expression) holds, match expr1, otherwise expr2 |
|
a comment, ignored when matching |
Lookaround assertions test the text before or after the current position without consuming it, so lookaheads can overlap: \w*(?=ing) on Flying, not falling. returns Fly and fall, the words that are followed by ing. Tokens are numbered from left to right by their opening parenthesis; for nested parentheses MATLAB captures the tokens for the outermost group only ((and(y|rew)) gives one token, andrew). A named token needs a name that is a valid identifier; do not confuse (?<name> with the lookbehind (?<= and (?<!.
Flags
(?i) ignores case, (?-i) matches case; (?s) lets . match a newline (the default) and (?-s) does not; (?m) makes ^ and $ match at line boundaries, (?-m) at the text boundaries (the default); (?x) ignores white space and comments in the expression (write a literal space as a backslash followed by a space or as [ ], and a literal # as \#), (?-x) includes them. A flag written alone applies to the rest of the expression, and (?i:expr) limits it to a group.
Dynamic expressions
A dynamic expression runs MATLAB code during the match: (??expr) parses expr as part of the pattern, (??@cmd) includes the output of the command cmd, and (?@cmd) runs cmd and discards its output, which helps in debugging. Inside them a replacement syntax gives access to the match ($&, $N for the Nth token, $<name> for a named token). The documentation’s example for palindromes, (.{2,}).?(??@fliplr($1)), finds abba. Dynamic expressions are a MATLAB feature; Octave does not support them, and they are not run here. They are rarely the right tool: extract the tokens with regexp and process them in ordinary code.
j = @(c) strjoin(c, ' | ');
j(regexp('<tr><td><p>text</p></td>', '</?t.*>', 'match'))
j(regexp('<tr><td><p>text</p></td>', '</?t.*?>', 'match'))
isempty(regexp('AtoZ', 'A(?>.*)Z', 'match'))
j(regexp('AtoZ', 'A(?:.*)Z', 'match'))
j(regexp('Flying, not falling.', '\w*(?=ing)', 'match'))
j(regexp('renew, reuse, recycle', '(?<=re)\w*', 'match'))
j(regexp('a1 b22 c333', '(?<!\d)(\d)(?!\d)', 'match'))
j(regexp(sprintf('l1\nl2'), '^l\d$', 'match', 'lineanchors'))
isempty(regexp(sprintf('l1\nl2'), '^l\d$', 'match'))
Octave 11.3.0 output (strjoin puts the matches of each call on one line):
ans = <tr><td><p>text</p></td>
ans = <tr> | <td> | </td>
ans = 1
ans = AtoZ
ans = Fly | fall
ans = new | use | cycle
ans = 1
ans = l1 | l2
ans = 1
The atomic group (?>.*) swallows the Z and there is no match (isempty is true), while the non-capturing group backtracks and succeeds. Without "lineanchors", ^l\d$ has to match the whole text, which is l1, a newline and l2, so there is no match (the last isempty is true).
Tokens, named tokens and replacement
Parentheses in an expression both group and capture. "tokens" returns, for each match, a cell with the captured text of each group; "names" returns a structure with one field per named token ((?<id>\d{2}) gives a field id), a structure array if there are several matches. Token text is always text, so convert numbers with str2double.
regexprep replaces the matches. In the replacement text $1, $2 … stand for the tokens; regexprep applies to every match unless you give "once", and it accepts arrays of text and of expressions (applied one after the other). If nothing matches, the output equals the input.
regexprep('TC03_x25_y50', '^TC(\d+)_x(\d+)_y(\d+)$', 'sensor $1: ($2,$3)')
regexprep(' too many spaces ', '\s+', ' ')
regexprep('Hello World', '(?i)world', 'MATLAB')
regexprep('aaa', 'a', 'b', 'once')
strjoin(regexprep({'ab','cb'}, 'b', 'X'), ' | ')
regexptranslate('escape', 'a.b*c')
regexptranslate('wildcard', '*.m')
Octave 11.3.0 output:
ans = sensor 03: (25,50)
ans = too many spaces
ans = Hello MATLAB
ans = baa
ans = aX | cX
ans = a\.b\*c
ans = .*\.m
Parsing hardware tags
This example is standalone and is not part of the heatflow data: the scenario’s sensors.csv identifies each sensor only by its SensorID (S1, S2, …). Suppose instead that a data-acquisition system labels each thermocouple channel with a hardware tag such as TC03_x25_y50: a two-digit channel number, then the position of the probe in millimetres. Structured text of this kind is where a regular expression fits. The expression has one named token for each part, anchored so that the whole tag must match:
label = 'TC03_x25_y50';
nm = regexp(label, '^TC(?<id>\d{2})_x(?<x>\d+)_y(?<y>\d+)$', 'names')
x = str2double(nm.x)
labels = {'TC03_x25_y50', 'TC12_x75_y10', 'TC7_x10', 'bad'};
ok = ~cellfun(@isempty, regexp(labels, '^TC\d{2}_x\d+_y\d+$', 'match', 'once'))
t = regexp(labels, '^TC(\d{2})_x(\d+)_y(\d+)$', 'tokens', 'once');
for k = find(ok)
v = str2double(t{k});
fprintf('%s -> sensor %d at (%d, %d)\n', labels{k}, v(1), v(2), v(3));
end
Octave 11.3.0 output (the names structure is shown in Octave’s layout):
nm =
scalar structure containing the fields:
id = 03
x = 25
y = 50
x = 25
ok =
1 1 0 0
TC03_x25_y50 -> sensor 3 at (25, 50)
TC12_x75_y10 -> sensor 12 at (75, 10)
regexp on a cell array of tags returns a cell array with one result per tag; with "once" a tag that does not match gives empty text (here TC7_x10 has one digit and no y, and bad has no structure), which cellfun(@isempty,...) turns into the mask ok. For a string array of tags the same calls return string arrays and cells of string arrays instead (not run here).
Source: MATLAB documentation: regexp
Pattern objects (R2020b)
A pattern describes text with functions, so the code reads like the description. The building blocks, from the reference page pattern and the topic "Build Pattern Expressions":
-
Character-matching patterns:
lettersPattern,digitsPattern,alphanumericsPattern,whitespacePattern,characterListPattern("+-*/="),wildcardPattern. Most take a count:digitsPattern(4)is exactly four digits,digitsPattern(2,3)two or three,digitsPatternalone one or more.digitsPatternis greedy;wildcardPatternis lazy, matching as few characters of any kind as needed (also none), and acceptsExcept=to exclude characters. -
Search rules:
optionalPattern(pat)(matchpatif possible, but not required),asManyOfPattern(pat)andasManyOfPattern(pat,min)(as many repeats as possible). -
Boundaries:
digitBoundary,alphanumericBoundaryand similar patterns match the edge between a run of that character type and other characters, not characters themselves. The operator~negates a boundary, and boundaries are useful as delimiters forsplit. -
Organization:
maskedPattern(pat)displays a variable name instead of a complicated expression, andnamedPattern(pat,"name")names a part so it can be read and changed by dot indexing (emailPattern.domain = "example.org"). -
Regular-expression patterns: the reference lists functions that embed a regular expression in a pattern expression, so the two styles can be mixed.
Patterns combine with the operator + (the parts in order, with literal text), | (one or the other), and ~ (for boundaries). A pattern is used by contains, startsWith, matches (the whole text matches), count, extract, replace, erase, insertAfter, and split. The regular-expression functions regexp, regexpi and regexprep take their expression as text.
label = "TC03_x25_y50";
pat = "TC" + digitsPattern(2) + "_x" + digitsPattern + "_y" + digitsPattern;
matches(label, pat) % true: the whole text fits
matches(["TC12_x75_y10" "TC7_x10"], pat) % [true false]
nums = extract(label, digitsPattern) % "03" "25" "50"
clean = replace(label, "_", " ") % "TC03 x25 y50"
readings = optionalPattern("-") + digitsPattern + optionalPattern("." + digitsPattern);
extract("a=21.4, b=-3, c=7.25", readings) % "21.4" "-3" "7.25"
Not run here (Octave 11.3.0 has no pattern objects); the comments give the results that follow from the rules of the reference page. digitsPattern matches one run of digits, so extract(label,digitsPattern) returns one element per run. optionalPattern("-") makes the minus sign and the fraction optional, which is how a decimal number with an optional sign is written without any escape characters.
The same task as the hardware-tag regular expression above is the line pat in this example, and the difference is the one the documentation points out: a pattern is longer but each part has a name, and nothing needs escaping (. and _ are literal text in a pattern, while . is a metacharacter in a regular expression). A pattern is not always the better tool: it has no tokens, so to get parts of a match you extract them with further patterns, and lookaround and backreferences are regular-expression features. A good rule is to use patterns for readable searching, extracting and replacing with the string functions, and regular expressions when you need tokens, lookaround, backreferences or compatibility with release before R2020b.
Pitfalls
| Pitfall | What to do |
|---|---|
|
Use the lazy form |
|
Escape it as |
A failed |
Test with |
|
Ask for |
Token text is text, even when it looks like a number |
Convert it with |
Nested parentheses give only the outer token |
Use |
|
Add |
Case-sensitive by default |
Use |
Zero-length matches are dropped |
Add |
Catastrophic backtracking on nested quantifiers such as |
Simplify the expression, or use an atomic group |
A pattern object where a regular expression is expected |
Patterns are for |
A pattern does not match because |
Use |
|
Parenthesize mixed expressions: |
Where older books differ
The older books and much existing code use regexp, regexprep and the character-vector functions (strfind, strrep, strtok, strsplit), which all remain valid and are unchanged. They do not have pattern objects, which appeared in R2020b, and they write text as single-quoted character vectors, so that regexp returns cell arrays of character vectors; with the string arrays of R2016b and later the outputs are string arrays (and cells of string arrays). The regular-expression syntax in the MATLAB documentation (lookaround, named tokens, dynamic expressions, flags) is the same as in older releases; the documentation now points beginners to patterns as the simpler alternative.