Celeb Glow
general | March 12, 2026

How can I do a recursive find and replace from the command line?

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.

3

10 Answers

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:

#!/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"
16
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

This removes the xargs dependency.

3

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

3

Try:

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

Tested on Ubuntu 12.04.

EDIT:

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"

2

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

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

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.

#!/bin/bash
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
old_val=$1
shift
new_val=$1
shift
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
done
1

My use case was I wanted to replace foo:/Drive_Letter with foo:/bar/baz/xyzIn 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/1.2.3.4\/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/1.2.3.4\/home/foo.com\/mnt/'
1
# 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

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy