Have you ever needed to perform a find and replace operation that spans across numerous files in your project? Doing this operation file by file can be tedious. Searching for an answer to this issue can be frustrating due to the involvement of many different Unix tools, each with their own flags. Some of these flags are incompatible between Linux and OS X, so it can quickly devolve into plugging in a solution, and moving on when it fails.
A few notes about my environment:
- Vim text editor
- Git source control
- Ack search tool
Here is a bullet-proof find and replace solution using the same search tool that I use inside of Vim - the wonderful Ack (http://beyondgrep.com/)
ack <search term> -l --print0 | xargs -0 sed -i '' 's/<search term>/<replacement term>/g'
- ack takes a search term, and with the
-l flag, it outputs only filenames
--print0 separates filenames with a null byte instead of a newline. This is nice for filenames that contain spaces (non programmers!)
- We take the resulting list and pipe it to
xargs which will iterate over each listing and applies the following command. Note the
-0 flag so that it knows to look for the null byte character
- Next comes
sed. The OS X implementation requires an extension following the
-i flag for the in place edit functionality. We pass it an empty string (
'') to tell it not to use an extension
- Finally, we do our sed function s/regular expression/replacement/flags.
s is for substitute, and
/g at the end means to make the substitutions for all matches
Why not ViM?
Vim doesn't support any quick global find and replace solutions. Most seem to involve filling the argbuffer, then iterating over these collections, or installing plugins. I was also having issues with prompts for saving my changes before changing buffers killing the looping operation. UPDATE Possible solutions for leaving modified buffers are documented here 1 I figured it was best to do this outside of ViM selecting from the full range of Unix tools.
Why not find or grep?
I favor ack because it is aware of the context of source code. It knows to skip log and tmp files, as well as version control meta data. The resulting operation is not only much faster, but you can be sure you aren't messing with any strange config settings in places that shouldn't be touched.
Why not apply sed to all the files?
Git is whitespace sensitive. Sed will touch the file regardless of whether changes were made or not. This means you could end up with a newline at the end of the file that you now have to either stage, or revert. While
git diff has a flag to ignore whitespace,
git add does not.
git add stages the files, so the solution would have involved piping the output from git diff into git add. This would have been silly since we can omit those files from being touched to begin with using