Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 4: Advanced Use Cases

This chapter covers advanced uses of regular expressions: complex patterns, recursion, conditional logic, and optimization techniques. The examples tackle harder text processing tasks than the ones in Chapter 3.

Some of these patterns use features that not every regex engine has. Recursion with (?R) works in PCRE (the engine behind PHP’s preg_ functions) and Perl, and in Python only through the third-party regex module. Atomic groups, (?>...), work in PCRE, Perl, Java, .NET, and Python 3.11 or newer. Conditionals such as (?(?=cat)cat|dog) work in PCRE, Perl, and .NET. JavaScript and Go’s standard regexp package support none of the three. Check your language’s documentation before you rely on them.

Complex pattern matching

Example 1: Matching balanced parentheses

Regex:

\(([^()]*|(?R))*\)

Explanation:

  • \( and \): Match a literal opening ( and closing ) parenthesis.
  • [^()]*: Matches any sequence of characters that are not parentheses. This part handles content within a single level of parentheses.
  • |: Acts as an OR operator, so the pattern after it is tried as an alternative.
  • (?R): This is a recursive pattern that allows the regex to match nested parentheses by calling itself.
  • *: Applies to the whole group, meaning zero or more occurrences of either content within parentheses or more nested parentheses.

Usage: Use it to check that parentheses are balanced and properly nested, for example in mathematical expressions or programming languages.

Example 2: Matching HTML tags

Regex:

<([a-zA-Z]+)([^<]+)*(?:>(.*)<\/\1>|\/>)

Explanation:

  • < and >: Match the opening and closing angle brackets of an HTML tag.
  • ([a-zA-Z]+): Captures the tag name, consisting of one or more letters.
  • ([^<]+)*: Matches any content within the tag that does not include a < character, capturing attributes.
  • (?: ... ): A non-capturing group that matches either a self-closing tag or a tag with content.
  • >(.*)<\/\1>: Matches the tag content, using .* to capture anything between the tags and \/\1 to ensure that the closing tag matches the opening tag captured in \1.
  • |\/>: Matches self-closing tags like <img />.

Usage: It extracts and validates HTML tags, which helps when you parse or sanitize HTML code.

Example 3: Matching delimited strings

Regex:

"(?:[^"\\]|\\.)*"

Explanation:

  • ": Matches the opening and closing double quotation marks.
  • (?: ... ): A non-capturing group that matches the content within the quotes.
  • [^"\\]: Matches any character that is not a double quote " or a backslash \.
  • |\\.: Matches any escaped character within the string, ensuring that sequences like \" or \\ are handled correctly.
  • *: Matches zero or more occurrences of the previous group, covering the entire content within the quotes.

Usage: It matches strings in double quotes, including any escaped characters inside them, as you find in JSON or programming languages.

Nested and recursive patterns

Example 1: Matching nested structures

Regex:

\((?>[^()]+|(?R))*\)

Explanation:

  • \( and \): Match the literal parentheses.
  • (?> ... ): An atomic group that prevents backtracking, ensuring that once a match is made, the regex engine does not reconsider it.
  • [^()]+: Matches one or more characters that are not parentheses.
  • |(?R): Allows recursion, where (?R) refers to the entire regex pattern, enabling the matching of nested parentheses.
  • *: Matches zero or more occurrences, handling multiple nested structures.

Usage: Use it to validate and extract data from nested structures such as mathematical expressions, nested lists, or code with several levels of parentheses.

Example 2: Matching nested lists

Regex:

\[(?>[^\[\]]+|(?R))*\]

Explanation:

  • \[ and \]: Match the opening and closing square brackets of a list.
  • (?>[^\[\]]+): Matches one or more characters that are not square brackets, preventing backtracking within the group.
  • |(?R): Recursively matches nested lists by calling the entire pattern again.
  • *: Matches zero or more occurrences of the previous groups, handling multiple nested lists.

Usage: It handles nested lists, such as JSON arrays or other hierarchical data formats.

Conditional expressions

Example 1: Conditional matching

Regex:

(?(?=regex)then|else)

Explanation:

  • (?=regex): A lookahead assertion that checks if the specified pattern matches at the current position.
  • then: The pattern to match if the condition is true.
  • else: The pattern to match if the condition is false.

Example:

(?(?=cat)cat|dog)
  • (?=cat): Checks if “cat” is present.
  • cat: Matches “cat” if the lookahead is true.
  • dog: Matches “dog” if the lookahead is false.

Usage: A conditional helps when what you want to match depends on the context.

Example 2: Matching optional prefixes

Regex:

\.(?(?=\d)\d+|\w+)

Explanation:

  • \.: Matches a literal dot.
  • (?(?=\d)\d+|\w+): Conditional that checks if a digit follows the dot.
    • (?=\d)\d+: If a digit is present, match one or more digits.
    • \w+: If a digit is not present, match one or more word characters.

Usage: It parses strings with optional prefixes. For example, it can tell file extensions like .123 and .txt apart by the first character after the dot.

Performance optimization

Example 1: Avoiding catastrophic backtracking

Regex:

(?>\d+|\w+)

Explanation:

  • (?> ... ): An atomic group that prevents backtracking, ensuring that once a match is made, it is not reconsidered.
  • \d+: Matches one or more digits.
  • \w+: Matches one or more word characters.

Usage: In complex patterns, backtracking can slow matching down a lot, so avoiding it makes a big difference to performance.

Example 2: Using non-capturing groups

Regex:

(?:\d+|\w+)

Explanation:

  • (?: ... ): A non-capturing group that groups elements without storing the matched text.
  • \d+: Matches one or more digits.
  • \w+: Matches one or more word characters.

Usage: Use a non-capturing group when you need to group part of a regex, for logic or for a quantifier, but don’t need the matched text. That can make the pattern both faster and easier to read.

Example 3: Using anchors for efficiency

Regex:

^\d+$

Explanation:

  • ^: Asserts the position at the start of the string.
  • \d+: Matches one or more digits.
  • $: Asserts the position at the end of the string.

Usage: Anchors like ^ and $ limit where the regex engine looks for a match, so it spends its time only on the parts of the text that matter.

Real-world examples and case studies

Example 1: Parsing log files

Regex:

\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b

Explanation:

  • \b: Word boundary ensures the IP address is matched as a whole.
  • \d{1,3}: Matches one to three digits, representing an octet of an IP address.
  • \.: Matches a literal dot, separating octets.
  • \d{1,3}: Repeated for each octet of the IP address.

Usage: Log parsers often use it to pull IP addresses out of server logs for later analysis of network traffic or security events.

Example 2: Extracting data from CSV files

Regex:

([^,]+),([^,]+),([^,]+)

Explanation:

  • ([^,]+): Captures one or more characters that are not a comma, representing a CSV field.
  • ,: Matches the literal comma separating fields.
  • ([^,]+): Repeated for subsequent fields.

Usage: It extracts the individual fields from CSV files, which makes tabular data easy to parse and process.

Example 3: Validating complex passwords

Regex:

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$

Explanation:

  • ^: Asserts the position at the start of the string.
  • (?=.*[a-z]): Positive lookahead ensuring at least one lowercase letter.
  • (?=.*[A-Z]): Positive lookahead ensuring at least one uppercase letter.
  • (?=.*\d): Positive lookahead ensuring at least one digit.
  • (?=.*[\W_]): Positive lookahead ensuring at least one special character or underscore.
  • .{8,}: Ensures the password is at least 8 characters long.
  • $: Asserts the position at the end of the string.

Usage: Use it to enforce a strong password policy. Every password has to meet the complexity requirements, which makes passwords more secure.

Example 4: Web scraping

Regex:

href="(https?://[^"]+)"

Explanation:

  • href=": Matches the href attribute in an HTML tag.
  • https?://: Matches either “http://” or “https://”.
  • [^"]+: Matches any sequence of characters that are not double quotes, representing the URL.
  • ": Matches the closing double quote of the href attribute.

Usage: Web scrapers often use it to pull URLs out of HTML anchor tags when they collect data from websites automatically.

Advanced text processing

Example 1: Text normalization

Regex:

\s+

Explanation:

  • \s+: Matches one or more whitespace characters (spaces, tabs, newlines).

Usage: Replacing each match with a single space normalizes the text, so later processing sees consistent spacing.

Example 2: Extracting keywords

Regex:

\b(?:keyword1|keyword2|keyword3)\b

Explanation:

  • \b: Word boundary ensures that the keywords are matched as whole words.
  • (?: ... ): Non-capturing group that groups the keywords without capturing them.
  • keyword1|keyword2|keyword3: Matches any of the specified keywords.

Usage: It picks specific keywords out of a text, which is handy in search engines, tagging systems, or text analysis.

Example 3: Advanced search and replace

Regex:

(?<=\bword1\b).+?(?=\bword2\b)

Explanation:

  • (?<=\bword1\b): Positive lookbehind that ensures the text is preceded by “word1”.
  • .+?: Matches any text between “word1” and “word2”, as few characters as possible due to the lazy quantifier ?.
  • (?=\bword2\b): Positive lookahead that ensures the text is followed by “word2”.

Usage: Use it for targeted search and replace, when you want to change only the text between two specific words.