可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying to search and replace a string in all files matched by grep on a linux machine. I've got some pieces of what I want to do, but I'm unsure how best to string them all together.
grep -n 'foo' *
will give me output in the form:
[filename]:[line number]:[text]
For each file returned by grep, I'd like replace "foo" with "bar" and write the result back to the file. Is there a good way to do that? Maybe a fancy pipeline?
回答1:
Do you mean search and replace a string in all files matched by grep?
perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`
Edit
Since this seems to be a fairly popular question thought I'd update.
Nowadays I mostly use ack-grep
as it's more user-friendly. So the above command would be:
perl -p -i -e 's/old/new/g' `ack -l searchpattern`
To handle whitespace in file names you can run:
ack --print0 -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'
you can do more with ack-grep
. Say you want to restrict the search to HTML files only:
ack --print0 --html -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'
And if white space is not an issue it's even shorter:
perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files
回答2:
This appears to be what you want, based on the example you gave:
sed -i 's/foo/bar/g' *
It is not recursive (it will not descend into subdirectories). For a nice solution replacing in selected files throughout a tree I would use find:
find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;
The *.html
is the expression that files must match, the .bak
after the -i
makes a copy of the original file, with a .bak extension (it can be any extension you like) and the g
at the end of the sed expression tells sed to replace multiple copies on one line (rather than only the first one). The -print
to find is a convenience to show which files were being matched. All this depends on the exact versions of these tools on your system.
回答3:
If your sed(1)
has a -i
option, then use it like this:
for i in *; do
sed -i 's/foo/bar/' $i
done
If not, there are several ways variations on the following depending on which language you want to play with:
ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *
回答4:
I like and used the above solution or a system wide search and replace among thousands of files:
find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;
I assume with the '*.htm?' instead of .html it searches and finds .htm and .html files alike.
I replace the .bak with the more system wide used tilde (~) to make clean up of backup files easier.
回答5:
This works using grep without needing to use perl or find.
grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @
回答6:
find . -type f -print0 | xargs -0 <sed/perl/ruby cmd>
will process multiple space contained file names at once loading one interpreter per batch. Much faster.
回答7:
The answer already given of using find and sed
find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;
is probably the standard answer. Or you could use perl -pi -e s/foo/bar/g'
instead of the sed
command.
For most quick uses, you may find the command rpl is easier to remember. Here is replacement (foo -> bar), recursively on all files in the current directory:
rpl -R foo bar .
It's not available by default on most Linux distros but is quick to install (apt-get install rpl
or similar).
However, for tougher jobs that involve regular expressions and back substitution, or file renames as well as search-and-replace, the most general and powerful tool I'm aware of is repren, a small Python script I wrote a while back for some thornier renaming and refactoring tasks. The reasons you might prefer it are:
- Support renaming of files as well as search-and-replace on file contents (including moving files between directories and creating new parent directories).
- See changes before you commit to performing the search and replace.
- Support regular expressions with back substitution, whole words, case insensitive, and case preserving (replace foo -> bar, Foo -> Bar, FOO -> BAR) modes.
- Works with multiple replacements, including swaps (foo -> bar and bar -> foo) or sets of non-unique replacements (foo -> bar, f -> x).
Check the README for examples.
回答8:
This is actually easier than it seems.
grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
- grep recurses through your tree (-R) and prints just the file name (-l), starting at the current directory (./)
- that gets piped to xargs, which processes them one at a time (-n 1), and uses % as a placeholder (-I %) in a shell command (sh -c)
- in the shell command, first the file name is printed (ls %;)
- then sed does an inline operation (-i), a substution('s/') of foo with bar (foo/bar), globally (/g) on the file (again, represented by %)
Easy peasy. If you get a good grasp on find, grep, xargs, sed, and awk, almost nothing is impossible when it comes to text file manipulation in bash :)