How can I do a recursive find and replace from the command line?
This command will do it (tested on both Mac OS X Lion and Kubuntu Linux).
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Here's how it works:
find . -type f -name '*.txt'
finds, in the current directory (.
) and below, all regular files (-type f
) whose names end in.txt
|
passes the output of that command (a list of filenames) to the next commandxargs
gathers up those filenames and hands them one by one tosed
sed -i '' -e 's/foo/bar/g'
means "edit the file in place, without a backup, and make the following substitution (s/foo/bar
) multiple times per line (/g
)" (seeman sed
)
Note that the 'without a backup' part in line 4 is OK for me, because the files I'm changing are under version control anyway, so I can easily undo if there was a mistake.
To avoid having to remember this, I use an interactive bash script, as follows:
#!/bin/bash
# find_and_replace.sh
echo "Find and replace in current directory!"
echo "File pattern to look for? (eg '*.txt')"
read filepattern
echo "Existing string?"
read existing
echo "Replacement string?"
read replacement
echo "Replacing all occurences of $existing with $replacement in files matching $filepattern"
find . -type f -name $filepattern -print0 | xargs -0 sed -i '' -e "s/$existing/$replacement/g"
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
This removes the xargs
dependency.
If you're using Git then you can do this:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
lists only filenames. -z
prints a null byte after each result.
I ended up doing this because some files in a project did not have a newline at the end of the file, and sed added a newline even when it made no other changes. (No comment on whether or not files should have a newline at the end. )