patternbashMinor
Find-grep-sed for project-wide search & replace
Viewed 0 times
searchsedgrepwidereplaceprojectforfind
Problem
I always forget how to do this efficiently with Vim's arglist. Drawing inspiration from a post over at Stack Overflow, I wrote a Bash function to perform a project-wide search & replace.
It does the following:
Example usage:
I'm on OSX, so unfortunately I had to use the ugly (and I assume non-portable)
I also debated whether to bake the word-boundaries in or leave them up to the caller (my poor little fingers). But I can't imagine wanting use this without word boundaries. I'd much rather miss a replacement because it's a variation on the search term than accidentally mangle a word happens to contain the search term.
Another potential issue is the fact that I'm using
Update Props to janos for his pointers and improvements. Here's an updated version:
``
It does the following:
- find files matching a file-pattern
- narrow down to those files containing a search-pattern
- within those files, replace all occurrences of the search-pattern
Example usage:
greplace **.rb old_method new_methodgreplace() {
if [ "${#}" != 3 ]; then
echo "Usage: greplace file_pattern search_pattern replacement"
return 1
else
file_pattern=$1
search_pattern=$2
replacement=$3
find . -name "${file_pattern}" |
xargs grep -rwl "${search_pattern}" |
xargs sed -i '' "s/[[::]]/${replacement}/g"
fi
}I'm on OSX, so unfortunately I had to use the ugly (and I assume non-portable)
[[:<:]] word-boundaries for sed. I tried \b and \<, with and without -E (for extended regular expressions), but still no dice.I also debated whether to bake the word-boundaries in or leave them up to the caller (my poor little fingers). But I can't imagine wanting use this without word boundaries. I'd much rather miss a replacement because it's a variation on the search term than accidentally mangle a word happens to contain the search term.
Another potential issue is the fact that I'm using
sed -i '' to perform the in-place replacements without making any backup files. This seems fine for my use, since I'm invariably using Git for version control. I'm curious how big a risk this is though.Update Props to janos for his pointers and improvements. Here's an updated version:
``
greplace() {
if [ "$#" != 3 ]; then
echo "Usage: greplace file_pattern search_pattern replacement"
return 1
else
file_pattern=$1
search_pattern=$2
replacement=$3
# This works with BSD grep and the sed bundled with OS X.
# GNU grep takes -Z instead of --null.
# Other versions of sed may not support the -i ''` Solution
Not bad so far!
You seem to like
In general, the best way to find files and do something to them is using the
The second best way is to pipe to
Your case has an added twist: you also want to pipe the matched filenames from
As for
It would be good to add comments near the top of the file to highlight this fact to whoever will inherit this script from you at some point in the future.
(Uhm, or in any case, for the "future you" ;-)
Lastly, I'm wondering if the
It's for recursive search in sub-directories, not only files.
If that's not intended, then drop that flag.
You seem to like
${name} instead of $name. That's fine, but it's not needed anywhere in the posted code. Usually it's needed in situations like ${name}_suffix, where without the ${...} the "_suffix" would become part of the variable name.In general, the best way to find files and do something to them is using the
-exec flag of find, for example:find . -name "$file_pattern" -exec grep -rwl "$search_pattern" {} \;
# or end with + to run on a single line
#find . -name "$file_pattern" -exec grep -rwl "$search_pattern" {} +The second best way is to pipe to
xargs -0, and use -print0, for example:find . -name "$file_pattern" -print0 | xargs -0 grep -rwl "$search_pattern"Your case has an added twist: you also want to pipe the matched filenames from
grep, and like find, the proper way is to use NUL character as the filename separator. In GNU grep the flag to achieve this is -Z. Putting the above together, the safest way to write your chained command:find . -name "${file_pattern}" -exec grep -Zrwl "${search_pattern}" \; |
xargs -0 sed -i '' "s/[[::]]/${replacement}/g"As for
[[:<:]], I don't know another way on OSX. In any case, the sed -i '' filaname usage won't work with GNU sed, so this script is not portable.It would be good to add comments near the top of the file to highlight this fact to whoever will inherit this script from you at some point in the future.
(Uhm, or in any case, for the "future you" ;-)
Lastly, I'm wondering if the
-r flag for grep is intentional.It's for recursive search in sub-directories, not only files.
If that's not intended, then drop that flag.
Code Snippets
find . -name "$file_pattern" -exec grep -rwl "$search_pattern" {} \;
# or end with + to run on a single line
#find . -name "$file_pattern" -exec grep -rwl "$search_pattern" {} +find . -name "$file_pattern" -print0 | xargs -0 grep -rwl "$search_pattern"find . -name "${file_pattern}" -exec grep -Zrwl "${search_pattern}" \; |
xargs -0 sed -i '' "s/[[:<:]]${search_pattern}[[:>:]]/${replacement}/g"Context
StackExchange Code Review Q#68244, answer score: 2
Revisions (0)
No revisions yet.