Ruby regex replace text, except text inside delimiter

Viewed 109

I would like to use Ruby's gsub to replace text except those inside a specific delimiter, like this:

regex = /(.+?)(@.+?@)(.+?)/
text = "aaa@xxx@bbb aaa@xxx@bbb"
text.gsub(regex){"#{$1.upcase}#{$2}#{$3.upcase}"}

I would like the return value to be AAA@xxx@BBB AAA@xxx@BBB, but it is AAA@xxx@BBB AAA@xxx@Bbb.

4 Answers

If you want to uppercase the left and right side after the @ chars:

([^\s@]+)((?:@[^\s@]+)*@)([^\s@]+)

The pattern matches:

  • ([^\s@]+) Capture group 1, match 1+ chars other than @ or a whitespace char
  • ( Capture group 2
    • (?:@[^\s@]+)*@ Optionally repeat matching @ followed by 1+ chars other than @, and then match at least a single @
  • ) Close group 2
  • ([^\s@]+) Capture group 3, match 1+ chars other than @ or a whitespace char

Regex demo | Ruby demo

regex = /([^\s@]+)((?:@[^\s@]+)*@)([^\s@]+)/
text = "aaa@xxx@bbb aaa@xxx@bbb aaa@bbb aaa@xxx@xxx@xxx@bbb"

puts(text.gsub(regex){"#{$1.upcase}#{$2}#{$3.upcase}"})

Output

AAA@xxx@BBB AAA@xxx@BBB AAA@BBB AAA@xxx@xxx@xxx@BBB

If you als want to match whitespace chars and newlines in between:

([^@]+)((?:@[^@]+)*@)([^@]+)

Try:

regex = /(?:@.*?@|@|\G)\K[^@]+/
text = "aaa@xxx@bbb aaa@bbb"
puts text.gsub(regex, &:upcase)

Prints:

AAA@xxx@BBB AAA@BBB

See an online demo


  • (?:@.*?@|@|\G) - Non-capture group to match either @ to the nearest @ or a single @ or match at the end of previous reported match;
  • \K[^@]+ - Reset match and then capture 1+ characters other than @.
str = "aaa@xxx@bbb aaa@xxx@bbb"
rgx = /@.*?@|([^@]+)/    
str.gsub(rgx) { |s| $1 ? s.upcase : s }
  #=> "AAA@xxx@BBB AAA@xxx@BBB"

The regular expression first attempts to match @.*?@ (equivalent to @[^@]*@ assuming the string does not contain newlines). If there is a match it will be held by the block variable s. As the match is not captured $1 #=> nil, so the match is replaced by itself.

If there is no match of @.*?@ an attempt is made to match ([^@]+). If there is a match, it is captured to group 1, so $1 in the block will hold a string (a truthy value), causing the match to be replaced with s.upcase (same as $1.upcase).

I have assumed that the string contains an even number of @'s, as the question does not say what is to be done if there are an odd number of @'s.

Does this resolve your question? If @ is the separator, then IMHO, splitting and uppercasing if even index in array would print the expected outcome.

text = "aaa@xxx@bbb aaa@xxx@bbb"
sArray = text.split(/@/)

finalText = ""

# for loop on sArray with index
sArray.each_with_index do |s, i|
    # if index is odd
    if i.even?
        finalText += s.upcase
    else
        finalText += s.downcase
    end
end

puts finalText
# AAAxxxBBB AAAxxxBBB
Related