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 of expression in str; regexpi is 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 *.m into 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

"start", "end"

the starting and ending indices of the matches (this is what regexp(str,expr) returns with no keyword, and with two outputs)

"tokenExtents"

the start and end indices of each token

"match"

the text of each match

"tokens"

the text of each token (the parts in parentheses) of each match

"names"

a structure with the name and text of each named token

"split"

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

"once"

return only the first match, as plain text instead of a cell

"ignorecase"

ignore letter case (regexpi does this by default)

"emptymatch"

keep zero-length matches, which are ignored by default

"dotexceptnewline"

make . match every character except a newline (by default it matches any character)

"lineanchors"

make ^ and $ match at the start and end of each line (by default, of the whole text)

"warnings"

display warnings (suppressed by default)

"forceCellOutput"

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.

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

[abc], [^abc]

any character in the set, or any character not in the set (the characters $ | . * + ? are literal inside brackets, and - is literal when not a range)

[a-z]

any character in the range

\w, \W

a word character (letter, digit, underscore), or any other character

\s, \S

a white-space character, or any other

\d, \D

a digit, or a non-digit

\xN, \oN

the character with hexadecimal or octal code N

\n, \t, \r, \f, \v, \a, \b

the control characters; \ before another special character matches it literally (\. is a dot, \\ a backslash)

Quantifiers

Element Number of times

expr*, expr+, expr?

0 or more, 1 or more, 0 or 1

expr{n}, expr{m,n}, expr{m,}

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

(expr)

group, and capture a token

(?:expr)

group without capturing

(?>expr)

atomic group: no backtracking inside, and no token

expr1|expr2

alternation: expr1, or expr2 (the first that matches wins)

^expr, expr$

the start and the end of the text (of each line with "lineanchors")

\<expr, expr\>

the start and the end of a word

Lookaround, tokens and conditions

Element Meaning

expr(?=test), expr(?!test)

expr, only if followed by test (lookahead), or not followed by it

(?<=test)expr, (?<!test)expr

expr, only if preceded by test (lookbehind), or not preceded

(expr) and \N

capture a token, and match the same text again (\1 is the first token)

(?<name>expr) and \k<name>

capture a named token, and match its text again

(?(cond)expr1|expr2)

if the condition (a token, a lookaround or a dynamic expression) holds, match expr1, otherwise expr2

(?#comment)

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).

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, digitsPattern alone one or more. digitsPattern is greedy; wildcardPattern is lazy, matching as few characters of any kind as needed (also none), and accepts Except= to exclude characters.

  • Search rules: optionalPattern(pat) (match pat if possible, but not required), asManyOfPattern(pat) and asManyOfPattern(pat,min) (as many repeats as possible).

  • Boundaries: digitBoundary, alphanumericBoundary and 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 for split.

  • Organization: maskedPattern(pat) displays a variable name instead of a complicated expression, and namedPattern(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

.* is greedy and swallows too much (<.*> spans several tags)

Use the lazy form <.*?> or a negated class <[^>]*>.

. is a metacharacter: "3.5" as a regular expression matches 3x5

Escape it as \., or build the expression with regexptranslate("escape",txt).

A failed "once" search returns empty text, not an empty cell

Test with isempty(...); without "once" the result is a cell, possibly empty.

regexp(str,expr) with no keyword returns indices, not text

Ask for "match" or "tokens".

Token text is text, even when it looks like a number

Convert it with str2double.

Nested parentheses give only the outer token

Use (?:...) for groups that should not capture, and name the tokens you need.

^ and $ match only the ends of the whole text

Add "lineanchors" for multi-line text.

Case-sensitive by default

Use regexpi, the "ignorecase" option, or (?i).

Zero-length matches are dropped

Add "emptymatch" when you want them.

Catastrophic backtracking on nested quantifiers such as (a+)+b

Simplify the expression, or use an atomic group (?>...) or a possessive quantifier.

A pattern object where a regular expression is expected

Patterns are for contains, extract, replace, matches and the related functions; regexp and regexprep take their expression as text.

A pattern does not match because matches needs the whole text

Use contains to search inside the text, and matches to test the whole.

+ in a pattern is concatenation, and | is alternation

Parenthesize mixed expressions: "R" + digitsPattern(4) + ("a" | "b").

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.