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?
sed -i 's/.*substring.*/replace/'
sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'
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' {} \;
I got the answer.
grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'
grep
and then again with sed
. Using find
method is more efficient but this method you mention does work.
sed -i 's/str1/str2/g'
to sed -i "" 's/str1/str2/g'
for this to work.
grep
goes through all the files and sed
only scans the files matched by grep
. With the find
method in the other answer, find
first lists all files, and then sed
will scan through all the files in that directory. So this method is not necessarily slower, it depends on how many matches there are and the differences in search speeds between sed
, grep
and find
.
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.
grep
is only useful if there are files which should not be modified. Running sed
on all files will update the file's modification date but leave the contents unchanged if there are no matches.
-i
, I believe sed
changes the file time of every file it touches, even though the contents are unchanged. sed
also converts line endings. I don't use sed
on Windows in a Git repo because all CRLF
are changed to LF
.
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
ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi
sort -u
even part of this? In what circumstances would you expect grep -rl
to produce the same file name twice?
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'
.
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
.
find2perl
which ships with Perl which does this sort of thing without any xargs
trickery.
find
doesn't search file contents, and the point is to only process matching files without writing a Perl program.
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
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
.
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
.
grep
and sed
.
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 :
Use fd and sd
fd -tf -x sd 'old([Ss]tring)' 'new$1' {}
Use ned
ned -R -p 'old([Ss]tring)' -r 'new$1' .
Use ruplacer
ruplacer --go 'old([Ss]tring)' 'new$1' .
Ignored files
To include ignored (by .gitignore
) and hidden files you have to specify it :
use -IH for fd,
use --ignored --hiddenfor ruplacer.
Success story sharing
-i
is required. For example,find /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" {} \;
Anyway, providing empty string still creates a backup file unlike described in manual...-i ""
for OS X. It works otherwise.CRLF
toLF
on Windows.