I understand the following:
$ ruby -e "p 'abc'.sub('a','A').sub('b', 'B')"
"ABc"
I am OK with the following also:
echo abc | ruby -p -e "sub('a','A');sub('b', 'B')"
ABc
But:
echo abc | ruby -p -e "sub('a','A').sub('b', 'B')"
Abc
I expect the result to be "ABc" as well, why is it not? The second sub('b', 'B')
is not operational.
CodePudding user response:
The two cases look similar, but in fact you are running different methods from the Ruby core library in them:
In your first case, i.e. sub('a','A');sub('b', 'B')
:
You are running both sub
without specifying an explicit receiver, and therefore you are invoking the method Kernel#sub
. The Ruby-Doc says about this method:
sub(pattern, replacement)
→$_
Equivalent to
$_.sub(args)
, except that$_
will be updated if substitution occurs. Available only when -p/-n command line option specified.
The following examples illustrate the differences with and without a receiver:
$ echo abc | ruby -p -e '$_.sub("a","A").sub("b", "B"); $_'
abc
$ echo abc | ruby -p -e 'sub("a","A").sub("b", "B"); $_'
Abc
Hence, in the first example, you really invoke that Kernel#sub
twice, and after each invocation, $_
is updated. Therefore, $_
is ABc after the second sub
has been executed. At the end of the of the whole expression supplied by -e
(i.e. at the end of the implicit loop provided by the -p
option), the value of $_
is printed, and you see ABc.
In your second example, i.e.
sub('a','A').sub('b', 'B')
The first sub
again is Kernel#sub
, as before. It has the effect of turning the string into Abc, and also sets $_
to Abc. However, the second sub
now does have an explicit receiver (the string resulting from the first sub
), and in this case, the method String#sub
is executed. This method produces ABc, but different to Kernel#sub
, it does not update $_
. Therefore, $_
is still set to Abc, and this is what you see as output.
While it seems to be convenient to rely on the implicit effect certain methods have on $_
, it is sometimes easier to make the manipulation explicit. For instance, if you do a
$_=$_.sub('a','A').sub('b','B')
you can clearly see what is going on. An alternative would be
$_.sub!('a','A');$_.sub!('b','B')
Note that in the last case, you do not want to chain the two sub!
, because String#sub! returns nil
if no substitutions have been performed.