Ruby: replacement part include $1 $2 $3 and saved in a variable, but interpolation doesn't happen when using gsub

139 views Asked by At

(a) the codes I wrote.

$str = '909090 aa bb cc dd ee ff 00 12345678 aa bb 12345678 aa bb cc dd ee ff 00 11 22 33 123456 FE 89'
puts $str
$str.gsub!(/\s+/, '')

search_1  = 'aa bb cc dd ee ff 00 (\S{8} aa bb \S{8} aa bb cc dd ee ff 00 11 22 33 \S{6}) (FF|FE) (89)'
puts search_1
search_1.gsub!(/\s+/, '')
replace_1 = '11 22 33 44 55 66 77 $1 $2 $3'
puts replace_1
replace_1.gsub!(/\s+/, '')

repls_1 = [
  [search_1, replace_1],
]


repls_1.each do |x|
  abc = $str.gsub(Regexp.new(x[0])) {
    x[1]
  }
  puts abc
end

(2) script running results

  909090 aa bb cc dd ee ff 00 12345678 aa bb 12345678 aa bb cc dd ee ff 00 11 22 33 123456 FE 89
  aa bb cc dd ee ff 00 (\S{8} aa bb \S{8} aa bb cc dd ee ff 00 11 22 33 \S{6}) (FF|FE) (89)
  11 22 33 44 55 66 77 $1 $2 $3

  90909011223344556677$1$2$3

The interpolation for $1, $2, $3 doesn't happen, is there way to solve it? Thanks.

(i) I don't want use \1 \2 \3, but $1 $2 $3)

(ii) There're more than one patterns, here only one example

(iii) matched parts numbers is not fixed, here is $1$2$3, other case is possible, such as ...$1...$2...$3...$5..., hope the codes can cover this variation on both captured parts number and position.

3

There are 3 answers

9
Rajagopalan On BEST ANSWER

In Ruby, the gsub method does not support $1, $2, $3 etc. for interpolation in the replacement string. However you can use them in a block given to gsub as given below. Try modifying this code.

repls_1.each do |x|
  abc = $str.gsub(Regexp.new(x[0])) {
    '11223344556677' + $1 + $2 + $3
  }
  puts abc
end

Edit

$~ is a special variable that holds the MatchData object for the last match. The captures method returns an array of all the groups in the match and join concatenates them together. This will work no matter how many groups your regex has. So here is the code.

repls_1.each do |x|
  abc = $str.gsub(Regexp.new(x[0])) {
    '11223344556677' + $~.captures.join
  }
  puts abc
end
1
Bridge IC On

Solution based on Rajagopalan's code (after study, put all $N at the end is fine, then this solution works.):

abc = $str.gsub(Regexp.new(x[0])) {
  s2 = $~.captures.join
  s1 = x[1].split(/\$/)[0]
  s1 + s2
}
2
Stefan On

I don't want use \1 \2 \3, but $1 $2 $3

You could dynamically replace $1, $2, $3 with their respective capture from the match before:

repls_1.each do |pattern, replacement|
  puts $str.gsub(Regexp.new(pattern)) {
    match_data = $~
    replacement.gsub(/\$(\d)/) { match_data[$1.to_i] }
  }
end

However, this is basically re-inventing the wheel because gsub has built-in replacement via \1, \2, \3 back-references.

It would be much easier to simply replace $1, $2, $3 with \1, \2, \3 to get a replacement string that's compatible with gsub. Since you want to keep your $-notation, you can perform this replacement at runtime, e.g. via tr:

repls_1.each do |pattern, replacement|
  gsub_replacement = replacement.tr('$', '\\')
  puts $str.gsub(Regexp.new(pattern), gsub_replacement)
end

You could also store the gsub-adjusted replacement string and an actual pattern in your repls_1 array already:

# create actual pattern and adjust replacement string
repls_1.map! { |a, b| [Regexp.new(a), b.tr('$', '\\')] }

This way, you don't have to repeat the adjustments every time and the replacement becomes even simpler (and faster):

repls_1.each do |pattern, replacement|
  puts $str.gsub(pattern, replacement)
end

Note that the code above replaces all $ with \. For a more sophisticated replacement, you could use another regex, e.g. to take a following digit into account:

gsub_replacement = replacement.gsub(/\$(?=\d)/, '\\')