Reputation: 6831
I need to recursively search for a specified string within all files and subdirectories within a directory and replace this string with another string.
I know that the command to find it might look like this:
grep 'string_to_find' -r ./*
But how can I replace every instance of string_to_find
with another string?
Upvotes: 371
Views: 564290
Reputation: 4023
Modern rust tools can be used to do this job. For example to replace in all (non ignored) files "oldstring" and "oldString" with "newstring" and "newString" respectively you can :
fd -tf -x sd 'old([Ss]tring)' 'new$1' {}
ned -R -p 'old([Ss]tring)' -r 'new$1' .
ruplacer --go 'old([Ss]tring)' 'new$1' .
To include ignored (by .gitignore
) and hidden files you have to specify it :
-IH
for fd
,--ignored --hidden
for ruplacer
.Upvotes: 0
Reputation: 9279
Other solutions mix regex syntaxes. To use perl/PCRE patterns for both search and replace, and process only matching files, this works quite well:
grep -rlIZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'
match1
and match2
are usually identical but match2
can contain more advanced features that are only relevant to the substitution, e.g. capturing groups.
Translation: grep
recursively and list matching filenames, each separated by null to protect any special characters; pipe any filenames to xargs
which is expecting a null-separated list; if any filenames are received, pass them to perl
to perform the actual substitutions.
For case-sensitive matching, drop the i
flag from grep
and the i
pattern modifier from the s///
expression, but not the i
flag from perl
itself. To include binary files, remove the I
flag from grep
.
Upvotes: 8
Reputation: 12757
You could even do it like this:
Example
grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'
This will search for the string 'windows' in all files relative to the current directory and replace 'windows' with 'linux' for each occurrence of the string in each file.
Upvotes: 75
Reputation: 386
Here is what I would do:
find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'
this will look for all files containing filename
in the file's name under the /path/to/dir
, than for every file found, search for the line with searchstring
and replace old
with new
.
Though if you want to omit looking for a specific file with a filename
string in the file's name, than simply do:
find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'
This will do the same thing above, but to all files found under /path/to/dir
.
Upvotes: 1
Reputation: 7952
Be very careful when using find
and sed
in a git repo! If you don't exclude the binary files you can end up with this error:
error: bad index file sha1 signature
fatal: index file corrupt
To solve this error you need to revert the sed
by replacing your new_string
with your old_string
. This will revert your replaced strings, so you will be back to the beginning of the problem.
The correct way to search for a string and replace it is to skip find
and use grep
instead in order to ignore the binary files:
sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")
Credits for @hobs
Upvotes: 6
Reputation: 768
Another option would be to just use perl with globstar.
Enabling shopt -s globstar
in your .bashrc
(or wherever) allows the **
glob pattern to match all sub-directories and files recursively.
Thus using perl -pXe 's/SEARCH/REPLACE/g' -i **
will recursively
replace SEARCH
with REPLACE
.
The -X
flag tells perl to "disable all warnings" - which means that
it won't complain about directories.
The globstar also allows you to do things like sed -i 's/SEARCH/REPLACE/g' **/*.ext
if you wanted to replace SEARCH
with REPLACE
in all child files with the extension .ext
.
Upvotes: -1
Reputation: 2278
This works best for me on OS X:
grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi
Source: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files
Upvotes: 42
Reputation: 5148
Another option is to use find and then pass it through sed.
find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;
Upvotes: 324
Reputation: 6831
I got the answer.
grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'
Upvotes: 260
Reputation: 4806
Usually not with grep, but rather with sed -i 's/string_to_find/another_string/g'
or perl -i.bak -pe 's/string_to_find/another_string/g'
.
Upvotes: 8