Can you make just part of a regex case-insensitive?

Solution 1:

Perl lets you make part of your regular expression case-insensitive by using the (?i:) pattern modifier.

Modern regex flavors allow you to apply modifiers to only part of the regular expression. If you insert the modifier (?ism) in the middle of the regex, the modifier only applies to the part of the regex to the right of the modifier. You can turn off modes by preceding them with a minus sign. All modes after the minus sign will be turned off. E.g. (?i-sm) turns on case insensitivity, and turns off both single-line mode and multi-line mode.

Not all regex flavors support this. JavaScript and Python apply all mode modifiers to the entire regular expression. They don't support the (?-ismx) syntax, since turning off an option is pointless when mode modifiers apply to the whole regular expressions. All options are off by default.

You can quickly test how the regex flavor you're using handles mode modifiers. The regex (?i)te(?-i)st should match test and TEst, but not teST or TEST.

Source

Solution 2:

It is true one can rely on inline modifiers as described in Turning Modes On and Off for Only Part of The Regular Expression:

The regex (?i)te(?-i)st should match test and TEst, but not teST or TEST.

However, a bit more supported feature is an (?i:...) inline modifier group (see Modifier Spans). The syntax is (?i:, then the pattern that you want to make cas-insensitive, and then a ).

(?i:foo)|BAR

The reverse: If your pattern is compiled with a case insensitive option and you need to make a part of a regex case sensitive, you add - after ?: (?-i:...).

Example uses in various languages (wrapping the matches with angle brackets):

  • php - preg_replace("~(?i:foo)|BAR~", '<$0>', "fooFOOfOoFoOBARBARbarbarbAr") (demo)
  • python - re.sub(r'(?i:foo)|BAR', r'<\g<0>>', 'fooFOOfOoFoOBARBARbarbarbAr') (demo) (note Python re supports inline modifier groups since Python 3.6)
  • c# / vb.net / .net - Regex.Replace("fooFOOfOoFoOBARBARbarbarbAr", "(?i:foo)|BAR", "<$&>") (demo)
  • java - "fooFOOfOoFoOBARBARbarbarbAr".replaceAll("(?i:foo)|BAR", "<$0>") (demo)
  • perl - $s =~ s/(?i:foo)|BAR/<$&>/g (demo)
  • ruby - "fooFOOfOoFoOBARBARbarbarbAr".gsub(/(?i:foo)|BAR/, '<\0>') (demo)
  • r - gsub("((?i:foo)|BAR)", "<\\1>", "fooFOOfOoFoOBARBARbarbarbAr", perl=TRUE) (demo)
  • swift - "fooFOOfOoFoOBARBARbarbarbAr".replacingOccurrences(of: "(?i:foo)|BAR", with: "<$0>", options: [.regularExpression])
  • go - (uses RE2) - regexp.MustCompile(`(?i:foo)|BAR`).ReplaceAllString( "fooFOOfOoFoOBARBARbarbarbAr", `<${0}>`) (demo)

Not supported in javascript, bash, sed, c++ std::regex, lua, tcl.

In these case, you can put both letter variants into a character class (not a group, see Why is a character class faster than alternation?). Examples:

  • sed posix-ere - sed -E 's/[Ff][Oo][Oo]|BAR/<&>/g' file > outfile (demo)
  • grep posix-ere - grep -Eo '[Ff][Oo][Oo]|BAR' file (demo)

Solution 3:

What language are you using? A standard way to do this would be something like /([Ff][Oo]{2}|BAR)/ with case sensitivity on, but in Java, for example, there is a case sensitivity modifier (?i) which makes all characters to the right of it case insensitive and (?-i) which forces sensitivity. An example of that Java regex modifier can be found here.