Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

share|improve this question
    
An alternative answer for the same questions can be found here stackoverflow.com/questions/9704020/… – dunxd Feb 13 '13 at 12:00
    
up vote 50 down vote accepted

This command will do it (tested on both Mac OS X Lion and Kubuntu Linux).

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

Here's how it works:

  1. find . -name '*.txt' finds, in the current directory (.) and below, all files 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.

share|improve this answer
6  
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 '12 at 23:16
13  
Also, just find -name '*.txt' -exec sed -i 's/foo/bar/g' {} + will do all this with GNU find. – Daniel Andersson May 25 '12 at 7:20
2  
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. – Martin Thoma Jan 30 '14 at 11:00
3  
This works for me if I remove the space between the -i and the '' – Canadian Luke Jun 2 '14 at 19:49
3  
What is the meaning of '' after the sed -i what is the '' role? – Jas Jul 28 '15 at 15:06
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

This removes the xargs dependency.

share|improve this answer
3  
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 '13 at 9:59
    
Good point. Fixed. Thanks! – Ztyx Jan 21 '13 at 10:16

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)

share|improve this answer

Try:

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.

http://linuxcommand.org/lc3_lts0020.php

Look at "Important facts about file names"

share|improve this answer
    
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.) – Scott Nov 8 '15 at 6:43

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
share|improve this answer

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.

share|improve this answer

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/'
share|improve this answer
    
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. – Scott Nov 8 '15 at 6:36

You must log in to answer this question.

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