> But sub! always returns nil, even when it succeeds (in Perl, it will 
> return the number of substitutions made, enabling "next if 
> s/\.$suf$//")

Er, no it doesn't.

irb(main):001:0> s="aaabbb"
=> "aaabbb"
irb(main):002:0> s.sub!(/a+/,'')
=> "bbb"
irb(main):003:0> s.sub!(/zzz/,'')
=> nil

C:\>ri String.sub!
------------------------------------------------------------ String#sub!
     str.sub!(pattern, replacement)          => str or nil
     str.sub!(pattern) {|match| block }      => str or nil
------------------------------------------------------------------------
     Performs the substitutions of +String#sub+ in place, returning
     _str_, or +nil+ if no substitutions were performed.