Once in a while you may need to do a big refactoring that neither your IDE nor your favorite editor can handle properly. Now you’re left with three options. Leave it to your colleagues, manually edit each file or learn sed.
In this very blog post I’ll show you how to apply the “Extract Method” refactoring across multiple files. Let’s assume we have a bunch of classes containing some duplications we want to remove.
class Foo
def initialize
self.bar
self.baz
self.foo
end
end
class Bar
def initialize
self.bar
self.baz
self.foo
end
end
After grepping through our code base, we decide on extracting a method called bar_baz_foo
.
The question is, how to do that? The answer: multi line search and replace.
This is were sed
comes into play.
Sed is a stream editor that performs editing operations on an input stream.
Most people will have seen the common substitute command either disguised as a Perl regex,
a vi command or in plain sed
# Replace occurrence of 'regex' with 'replacement'
sed 's/regex/replacement/'
The interesting part is that the regular expression given applies to the so called pattern space. Without any further modification the pattern space just holds the current line as read from the input. But sed has some tricks up its sleeves that allows for manipulation of the pattern space. For example the above command can be prefixed with an address. This address determines for which lines the command should be executed.
# Replace occurrence of 'regex' with 'replacement'
# only in lines 1 to 5
sed '1,5s/regex/replacement/'
The address itself isn’t constrained to only line numbers. We can use regular expressions here as well.
# Replace occurrence of 'regex' with 'replacement'
# only in lines matching the regex 'foo'
sed '/foo/s/regex/replacement/'
Well, OK, boring. Let’s get to the nitty gritty.
# Only consider lines matching 'self.bar'
# Append the following two lines to the pattern space
# Keep the same indent by matching whitespace
# Replace the 3 lines with our new method
# while keeping the indent
# `sed -i` applies the change in place.
sed -i '/self.bar/{N;N;s/^\([[:blank:]]*\).*/\1self.bar_baz_foo/}' foo.rb
Now if you combine this with find -exec
or xargs
you get a decent
multi line search and replace work flow. If you think you need to dig
in deeper don’t hesitate to join #sed
on Freenode or look at the
sed FAQ. Have fun!