ruby sub when replacement string starts with \0 - ruby

In ruby, sub does not allow to replace a string by another one starting with '\0'.
'a'.sub('a','\\0b')
Returns:
'ab'
The doc says that \0 is interpreted as a backreference, but as the first parameter is not a Regexp, I don't understand why it works like that.

If you want your second argument to be interpreted as a plain String you can escape it like:
'a'.sub('a', Regexp.escape('\0b'))
or
'a'.sub('a', '\\\0b')
both returns:
"\\0b"
Explanation about this behaviour can be found in documentation
sub(pattern, replacement) → new_str
The pattern is typically a Regexp; if given as a String, any regular
expression metacharacters it contains will be interpreted literally,
e.g. '\d' will match a backslash followed by 'd', instead of a digit.
If replacement is a String it will be substituted for the matched
text. It may contain back-references to the pattern's capture groups
of the form "\d", where d is a group number, or "\k<n>", where n is a
group name. If it is a double-quoted string, both back-references must
be preceded by an additional backslash. However, within replacement
the special match variables, such as $&, will not refer to the current
match. If replacement is a String that looks like a pattern's capture
group but is actually not a pattern capture group e.g. "\'", then it
will have to be preceded by two backslashes like so "\'".

Related

Ruby method gsub with string '+'

I've found interesting thing in ruby. Do anybody know why is behavior?
tried '+'.gsub!('+', '\+') and expected "\\+" but got ""(empty string)
gsub is implemented, after some indirection, as rb_sub_str_bang in C, which calls rb_reg_regsub.
Now, gsub is supposed to allow the replacement string to contain backreferences. That is, if you pass a regular expression as the first argument and that regex defines a capture group, then your replacement string can include \1 to indicate that that capture group should be placed at that position.
That behavior evidently still happens if you pass an ordinary, non-regex string as the pattern. Your verbatim string obviously won't have any capture groups, so it's a bit silly in this case. But trying to replace, for instance, + with \1 in the string + will give the empty string, since \1 says to go get the first capture group, which doesn't exist and hence is vacuously "".
Now, you might be thinking: + isn't a number. And you'd be right. You're replacing + with \+. There are several other backreferences allowed in your replacement string. I couldn't find any official documentation where these are written down, but the source code does quite fine. To summarize the code:
Digits \1 through \9 refer to numbered capture groups.
\k<...> refers to a named capture group, with the name in angled brackets.
\0 or \& refer to the whole substring that was matched, so (\0) as a replacement string would enclose the match in parentheses.
A backslash followed by a backtick (I have no idea how to write that using StackOverflow's markdown) refers to the entire string up to the match.
\' refers to the entire string following the match.
\+ refers to the final capture group, i.e. the one with the highest number.
\\ is a literal backslash.
(Most of these are based on Perl variables of a similar name)
So, in your examples,
\+ as the replacement string says "take the last capture group". There is no capture group, so you get the empty string.
\- is not a valid backreference, so it's replaced verbatim.
\ok is, likewise, not a backreference, so it's replaced verbatim.
In \\+, Ruby eats the first backslash sequence, so the actual string at runtime is \+, equivalent to the first example.
For \\\+, Ruby processes the first backslash sequence, so we get \\+ by the time the replacement function sees it. \\ is a literal backslash, and + is no longer part of an escape sequence, so we get \+.

How does 'String#gsub { }' (with a block) work?

When I do,
> "fooo".gsub("o") {puts "Found an 'o'"}
Found an 'o'
Found an 'o'
Found an 'o'
=> "f"
gsub removes all 'o's. How does this work?
I think gsub passes each character to the block, but since block is doing nothing to the character itself (like catching it), it is dropped.
I think this is the case because, when I do
> "fooo".gsub("o"){|ch| ch.upcase}
=> "fOOO"
the block is catching the character and turning it into uppercase.
But when I do,
> "fooo".gsub("o", "u"){|ch| ch.upcase}
=> "fuuu"
How does Ruby handle the block in this case?
I found that Ruby plugs the blocks into methods using yield. (check this) But I am still not sure about my explanation for the first code example and third example. Can anyone put some more light on this?
The documentation of method String#gsub explains how it works, depending of what parameters it gets:
gsub(pattern, replacement) → new_str
gsub(pattern, hash) → new_str
gsub(pattern) {|match| block } → new_str
gsub(pattern) → enumerator
Returns a copy of str with all occurrences of pattern substituted for the second argument. The pattern is typically a Regexp; if given as a String, any regular expression metacharacters it contains will be interpreted literally, e.g. \\d will match a backslash followed by d, instead of a digit.
If replacement is a String it will be substituted for the matched text. It may contain back-references to the pattern’s capture groups of the form \\d, where d is a group number, or \\k<n>, where n is a group name. If it is a double-quoted string, both back-references must be preceded by an additional backslash. However, within replacement the special match variables, such as $&, will not refer to the current match.
If the second argument is a Hash, and the matched text is one of its keys, the corresponding value is the replacement string.
In the block form, the current match string is passed in as a parameter, and variables such as $1, $2, $`, $&, and $' will be set appropriately. The value returned by the block will be substituted for the match on each call.
The result inherits any tainting in the original string or any supplied replacement string.
When neither a block nor a second argument is supplied, an Enumerator is returned.
The answer to your question looks straightforward now.
When only one argument is passed (the pattern), "the value returned by the block will be substituted for the match on each call".
Two arguments and a block is a case not covered by the documentation because it is not a valid combination. It seems that when two arguments are passed, String#gsub doesn't expect a block and ignores it.
Update
The purpose of String#gsub is to do a "global search", i.e. find all occurrences of some string or pattern and replace them.
The first argument, pattern, is the string or pattern to search for. There is nothing special about it. It can be a string or a regular expression. String#gsub searches it and finds zero or more matches (occurrences).
With only one argument and no block, String#gsub returns an iterator because it can find the pattern but it doesn't have a replacement string to use.
There are three ways to provide it the replacements for the matches (the first three cases described in the documentation quoted above):
a String is used to replace all the matches; it is usually used to remove parts from a string (by providing the empty string as replacement) or mask fragments of it (credit card numbers, passwords, email addresses etc);
a Hash is used to provide different replacements for each match; it is useful when the matches are known in advance;
a block is provided when the replacements depend on the matched substrings but the matches are not known in advance; for example, a block can convert each matching substring to uppercase and return it to let String#gsub use it as replacement.
The return value of puts is nil, which is converted to blank by to_s. Hence, each matched "o" is replaced with blank.

Ruby Regexp gsub, replace instances of second matching character

I would like to replace the first letter after a hyphen in a string with a capitalised letter.
"this-is-a-string" should become "thisIsAString"
"this-is-a-string".gsub( /[-]\w/, '\1'.upcase )
I was hoping that \1 would reinsert my second character match \w and that I could capitalise it.
How does one use the \0 \1 etc options?
You need to capture \w to be able to refer to the submatch.
Use
"this-is-a-string".gsub(/-(\w)/) {$~[1].upcase}
# => thisIsAString
See the Ruby demo
Note that $~[1] inside the {$~[1].upcase} block is actually the text captured with (\w), the $~ is a matchdata object instantiated with gsub and [1] is the index of the first group defined with a pair of unescaped parentheses.
See more details about capturing groups in the Use Parentheses for Grouping and Capturing section at regular-expressions.info.

What does <\1> mean in String#sub?

I was reading this and I did not understand it. I have two questions.
What is the difference ([aeiou]) and [aeiou]?
What does <\1> mean?
"hello".sub(/([aeiou])/, '<\1>') #=> "h<e>llo"
Here it documented:
If replacement is a String it will be substituted for the matched text. It may contain back-references to the pattern’s capture groups of the form "\d", where d is a group number, or "\k<n>", where n is a group name. If it is a double-quoted string, both back-references must be preceded by an additional backslash. However, within replacement the special match variables, such as &$, will not refer to the current match.
Character Classes
A character class is delimited with square brackets ([, ]) and lists characters that may appear at that point in the match. /[ab]/ means a or b, as opposed to /ab/ which means a followed by b.
Hope above definition made clear what [aeiou] is.
Capturing
Parentheses can be used for capturing. The text enclosed by the nth group of parentheses can be subsequently referred to with n. Within a pattern use the backreference \n; outside of the pattern use MatchData[n].
Hope above definition made clear what ([aeiou]) is.
([aeiou]) - any characters inside the character class [..],which will be found first from the string "hello",is the value of \1(i.e.the first capture group). In this example value of \1 is e,which will be replaced by <e> (as you defined <\1>). That's how "h<e>llo" has been generated from the string hello using String#sub method.
The doc you post says
It may contain back-references to the pattern’s capture groups of the
form "\d", where d is a group number, or "\k", where n is a group
name.
So \1 matches whatever was captured in the first () group, i.e. one of [aeiou] and then uses it in the replacement <\1>

Backslash + captured group within Ruby regular expression

How do I excape a backslash before a captured group?
Example:
"foo+bar".gsub(/(\+)/, '\\\1')
What I expect (and want):
foo\+bar
what I unfortunately get:
foo\\1bar
How do I escape here correctly?
As others have said, you need to escape everything in that string twice. So in your case the solution is to use '\\\\\1' or '\\\\\\1'. But since you asked why, I'll try to explain that part.
The reason is that replacement sequence is being parsed twice--once by Ruby and once by the underlying regular expression engine, for whom \1 is its own escape sequence. (It's probably easier to understand with double-quoted strings, since single quotes introduce an ambiguity where '\\1' and '\1' are equivalent but '\' and '\\' are not.)
So for example, a simple replacement here with a captured group and a double quoted string would be:
"foo+bar".gsub(/(\+)/, "\\1") #=> "foo+bar"
This passes the string \1 to the regexp engine, which it understands as a reference to a capture group. In Ruby string literals, "\1" means something else entirely (ASCII character 1).
What we actually want in this case is for the regexp engine to receive \\\1. It also understands \ as an escape character, so \\1 is not sufficient and will simply evaluate to the literal output \1. So, we need \\\1 in the regexp engine, but to get to that point we need to also make it past Ruby's string literal parser.
To do that, we take our desired regexp input and double every backslash again to get through Ruby's string literal parser. \\\1 therefore requires "\\\\\\1". In the case of single quotes one slash can be omitted as \1 is not a valid escape sequence in single quotes and is treated literally.
Addendum
One of the reasons this problem is usually hidden is thanks to the use of /.+/ style regexp quotes, which Ruby treats in a special way to avoid the need to double escape everything. (Of course, this doesn't apply to gsub replacement strings.) But you can still see it in action if you use a string literal instead of a regexp literal in Regexp.new:
Regexp.new("\.").match("a") #=> #<MatchData "a">
Regexp.new("\\.").match("a") #=> nil
As you can see, we had to double-escape the . for it to be understood as a literal . by the regexp engine, since "." and "\." both evaluate to . in double-quoted strings, but we need the engine itself to receive \..
This happens due to a double string escaping. You should use 5 slashes in this case.
"foo+bar".gsub(/([+])/, '\\\\\1')
Adding \ two more times escapes this properly.
irb(main):011:0> puts "foo+bar".gsub(/(\+)/, '\\\\\1')
foo\+bar
=> nil

Resources