Using a shell like bash or zshell, how can I do a recursive 'find and replace'? In other words, I want to replace every occurrence of 'foo' with 'bar' in all files in this directory and its subdirectories.


10 Answers 10


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:

  1. find . -type f -name '*.txt' finds, in the current directory (.) and below, all regular files (-type f) whose names end in .txt
  2. | passes the output of that command (a list of filenames) to the next command
  3. xargs gathers up those filenames and hands them one by one to sed
  4. 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)" (see man 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:

# 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"
  • 14
    Never ever pipe find output to xargs without the -print0 option. Your command will fail on files with spaces etc. in their name.
    – slhck
    May 24, 2012 at 23:16
  • 33
    Also, just find -name '*.txt' -exec sed -i 's/foo/bar/g' {} + will do all this with GNU find. May 25, 2012 at 7:20
  • 9
    I get sed: can't read : No such file or directory when I run find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', but find . -name '*.md' -print0 gives a list of many files. Jan 30, 2014 at 11:00
  • 14
    This works for me if I remove the space between the -i and the '' Jun 2, 2014 at 19:49
  • 6
    What is the meaning of '' after the sed -i what is the '' role?
    – Jas
    Jul 28, 2015 at 15:06
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

This removes the xargs dependency.

  • 4
    This does not work with GNU sed, so will fail on most systems. GNU sed requires you to put no space between -i and ''.
    – slhck
    Jan 16, 2013 at 9:59
  • 2
    The accepted answers does a better job of explaining it. but +1 for using the correct syntax.
    – orodbhen
    Nov 18, 2016 at 17:10
  • 1
    -i'' is exactly the same as -i.
    – Carsten S
    Jan 7, 2021 at 16:35

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. 🙂)

  • 2
    Big +1 for this solution. The rest of the find ... -print0 | xargs -0 sed ... solutions not only take a lot longer but also add newlines to files that don't have one already, which is a nuisance when working inside a git repo. git grep is lighting fast by comparison. Sep 30, 2016 at 19:56
  • Not sure what the single quote pair does in the sed command. I think it was causing a "no such file" error. Worked great without them. Dec 28, 2020 at 23:04
  • May be a difference between gnu sed and other sed implementations. Dec 30, 2020 at 4:23
  • This brought down the time taken for my script from 7 minutes to 40 seconds! Thank you for this solution Jul 7 at 22:15


sed -i 's/foo/bar/g' $(find . -type f)

Tested on Ubuntu 12.04.


This command will NOT work if subdirectory names and/or filenames contain spaces, but if you do have them don't use this command as it won't work.

It is generally a bad practice to use spaces in directory names and filenames.


Look at "Important facts about file names"

  • 1
    Try it when you have file(s) with space(s) in their names.  (There's a rule of thumb that says, "If something seems too good to be true, it probably is."  If you have "discovered" a solution that's more compact than anything anybody else has posted in 3½ years, you should ask yourself why that might be.) Nov 8, 2015 at 6:43
  • 1
    Files with spaces are fairly uncommon in linux. Fair to list the caveat but IMO this is the best answer.
    – Jordan
    Jan 14, 2022 at 19:31

Here's my zsh/perl function I use for this:

change () {
        for file in $*
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"

And I'd execute it using

$ change foo bar **/*.java

(for example)


Use This Shell Script

I now use this shell script, which combines things I learned from the other answers and from searching the web. I placed it in a file called change in a folder on my $PATH and did chmod +x change.

function err_echo {
  >&2 echo "$1"

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1

[ $# -eq 0 ] && err_echo "No args given" && usage

files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
  • 1
    so which of your two answers you prefer?
    – David
    Mar 16, 2021 at 5:29

My use case was I wanted to replace foo:/Drive_Letter with foo:/bar/baz/xyz In my case I was able to do it with the following code. I was in the same directory location where there were bulk of files.

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

hope that helped.


The following command worked fine on Ubuntu and CentOS; however, under OS X I kept getting errors:

find . -name Root -exec sed -i 's/\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": invalid command code .

When I tried passing the params via xargs it worked fine with no errors:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/\/home/foo.com\/mnt/'
  • The fact that you changed -i to -i '' is probably more relevant than the fact that you changed -exec to -print0 | xargs -0.  BTW, you probably don't need the -e. Nov 8, 2015 at 6:36
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

The above worked like a charm, but with linked directories, I've to add -L flag to it. The final version looks like:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Using zsh globbing and sed

(Tested with sed on MacOS - may vary a bit on Linux)

sed -i '' -e 's/foo/bar/g' somefolder/**/*.txt

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .