49

what's the best way to do this? I'm no command line warrior, but I was thinking there's possibly a way of using grep and cat.

I just want to replace a string that occurs in a folder and sub-folders. what's the best way to do this? I'm running ubuntu if that matters.

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
damon
  • 14,485
  • 14
  • 56
  • 75
  • Just going to leave this link right over here: http://stackoverflow.com/questions/102083/whats-the-best-tool-to-find-and-replace-regular-expressions-over-multiple-files – Ehtesh Choudhury Jun 07 '12 at 02:59

7 Answers7

49

I'll throw in another example for folks using ag, The Silver Searcher to do find/replace operations on multiple files.

Complete example:

ag -l "search string" | xargs sed -i '' -e 's/from/to/g'

If we break this down, what we get is:

# returns a list of files containing matching string
ag -l "search string"

Next, we have:

# consume the list of piped files and prepare to run foregoing command
# for each file delimited by newline
xargs

Finally, the string replacement command:

# -i '' means edit files in place and the '' means do not create a backup
# -e 's/from/to/g' specifies the command to run, in this case,
# global, search and replace

sed -i '' -e 's/from/to/g'
Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
doremi
  • 14,921
  • 30
  • 93
  • 148
  • 5
    This is no longer working for me. I keep getting the error "sed can't read: No such file or directory" – Lance Nov 05 '19 at 18:33
  • 2
    You should do ```ag -l "text" | xargs -I FILE sed -i 's/from/to/g' FILE``` – Dmitry Feb 21 '20 at 13:44
  • 1
    The man says `-i[SUFFIX], --in-place[=SUFFIX]: edit files in place (makes backup if SUFFIX supplied)`. That's means it should be `ag -l "search string" | xargs sed -i -e 's/from/to/g'` without value after the `-i` else I got same error than @Lance – Bastien Adam Jan 07 '21 at 02:49
42
find . -type f -print0 | xargs -0 -n 1 sed -i -e 's/from/to/g'

The first part of that is a find command to find the files you want to change. You may need to modify that appropriately. The xargs command takes every file the find found and applies the sed command to it. The sed command takes every instance of from and replaces it with to. That's a standard regular expression, so modify it as you need.

If you are using svn beware. Your .svn-directories will be search and replaced as well. You have to exclude those, e.g., like this:

find . ! -regex ".*[/]\.svn[/]?.*" -type f -print0 | xargs -0 -n 1 sed -i -e 's/from/to/g'

or

find . -name .svn -prune -o -type f -print0 | xargs -0 -n 1 sed -i -e 's/from/to/g'
Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • it doesn't seem like this command is recursing into subdirectories. is there something i'm doing wrong? – damon Feb 09 '09 at 19:16
  • What makes you think it's not recursing into directories? – Paul Tomblin Feb 09 '09 at 19:24
  • 1
    the text files in the subdirectories are not making the changes. what i want it to do is this: change occurrences of some.ip.address to somewebsite.com in all subdirs. – damon Feb 09 '09 at 20:17
  • Replace the "sed" with "echo" and make sure it's finding all your files. – Paul Tomblin Feb 09 '09 at 20:27
  • If that change shows you that it's finding your files, then the fault is in your regular expression. Post what you're using in the 's/some.ip.address/somewebsite.com/g' part of the sed command. – Paul Tomblin Feb 09 '09 at 20:29
  • for the sed part i'm using: find . -type f -print0 | xargs -0 -n 1 sed -i -e 's/some.\ip\.address/somewebsite\.com/g' – damon Feb 09 '09 at 20:41
  • Try "find . -type f -print0 | xargs -0 -n 1 grep -l 'some\.ip\.address'" to see if it's actually finding your ip address. – Paul Tomblin Feb 09 '09 at 20:45
  • It's also possible you have an old version of sed that doesn't support -i. – Paul Tomblin Feb 09 '09 at 20:47
  • if i look at sed --help, it shows this: -i[SUFFIX], --in-place[=SUFFIX] edit files in place (makes backup if extension supplied) – damon Feb 09 '09 at 20:50
  • 1
    I'm on a Mac (Lion) and I had to add an empty string to cause no suffix to be applied i.e.: `-i ''`, otherwise it interprets the `-e` to be the suffix and renames all files. Full example: `find . -type f -name '*.js' -print0 | xargs -0 -n 1 sed -i '' -e 's/from/to/g'` (replaces in all javascript files). – Thorsten Lorenz Jan 04 '13 at 15:04
37

As Paul said, you want to first find the files you want to edit and then edit them. An alternative to using find is to use GNU grep (the default on Ubuntu), e.g.:

grep -r -l from . | xargs -0 -n 1 sed -i -e 's/from/to/g'

You can also use ack-grep (sudo apt-get install ack-grep or visit http://petdance.com/ack/) as well, if you know you only want a certain type of file, and want to ignore things in version control directories. e.g., if you only want text files,

ack -l --print0 --text from | xargs -0 -n 1 sed -i -e 's/from/to/g'
# `from` here is an arbitrary commonly occurring keyword

An alternative to using sed is to use perl which can process multiple files per command, e.g.,

grep -r -l from . | xargs perl -pi.bak -e 's/from/to/g'

Here, perl is told to edit in place, making a .bak file first.

You can combine any of the left-hand sides of the pipe with the right-hand sides, depending on your preference.

Sridhar Ratnakumar
  • 81,433
  • 63
  • 146
  • 187
Emil Sit
  • 22,894
  • 7
  • 53
  • 75
  • You can avoid version control directories in "find" using the "-prune" command. For instance "find . -name RCS -prune -o -type f -print0" – Paul Tomblin Feb 09 '09 at 19:04
  • True, though it requires less brainpower to have a tool like ack handle it for you. – Emil Sit Feb 10 '09 at 13:44
  • ack also accepts a `--print0` option to use null as a delimiter between file names, so your ack command would look like this: `ack -l --print0 --text from | xargs -0 -n 1 sed -i -e 's/from/to/g'` – Josh Strater May 31 '11 at 15:10
  • Thanks, fixed the text inline. – Emil Sit May 31 '11 at 15:57
  • 1
    Watch out as grep, ack, perl, sed do not all use the same regex variant. Ack being written in perl, you can use the same `from` clause in both if you pipe `ack` into `perl`. – Lloeki May 25 '12 at 13:29
  • 1st solution for some reason does not work on OS X ... seems that the whole list of filenames returned by grep is considered a single file name – Petr Peller Apr 17 '16 at 11:04
3

An alternative to sed is using rpl (e.g. available from http://rpl.sourceforge.net/ or your GNU/Linux distribution), like rpl --recursive --verbose --whole-words 'F' 'A' grades/

  • How would you use this in a script? I finally settled on: `ag -l "$1" | xargs sed -i '' -e "s/$1/$2/g"` but I'm concerned about the argument expansion after removing the single quotes – Connor Oct 22 '18 at 02:47
3

For convenience, I took Ulysse's answer (after correcting the undesirable error printing) and turned it into a .zshrc / .bashrc function:

function find-and-replace() {
  ag -l "$1" | xargs sed -i -e s/"$1"/"$2"/g
}

Usage: find-and-replace Foo Bar

Samuel-Zacharie Faure
  • 1,047
  • 11
  • 26
2

The typical (find|grep|ack|ag|rg)-xargs-sed combination has a few problems:

  • Difficult to remember and get correct. Eg, forgetting the xargs -r option will run the command even when no files are found, potentially causing problems.
  • Retrieving the file list, and the actual replacement uses different CLI tools and can have a different search behaviour.

These problems were big enough for such an invasive and dangerous operation as recursive search-and-replace, to start the development of a dedicated tool: mo.

Early tests seem to indicate that its performance is between ag and rg and it solves following problems I encounter with them:

  • A single invocation can filter on filename and content. Following command searches for the word bug in all source files that have a v1 indication:

    mo -f 'src/.*v1.*' -p bug -w

  • Once the search results are OK, actual replacement for bug with fix can be added:

    mo -f 'src/.*v1.*' -p bug -w -r fix

  • I think it would be important to explicitly mention that you're the main author of the tool you're promoting. – kelvin May 29 '21 at 02:45
1
comment() { 
}
doc() { 
}
function agr { 
doc 'usage: from=sth to=another agr [ag-args]'
comment -l --files-with-matches

ag -0 -l "$from" "${@}" | pre-files "$from" "$to"
}
pre-files() {
doc 'stdin should be null-separated list of files that need replacement; $1 the string to replace, $2 the replacement.'
comment '-i backs up original input files with the supplied extension (leave empty for no backup; needed for in-place replacement.)(do not put whitespace between -i and its arg.)'
comment '-r, --no-run-if-empty
              If  the  standard input does not contain any nonblanks,
              do not run the command.  Normally, the command  is  run
              once  even  if there is no input.  This option is a GNU
              extension.'

AGR_FROM="$1" AGR_TO="$2" xargs -r0 perl -pi.pbak -e 's/$ENV{AGR_FROM}/$ENV{AGR_TO}/g'
}

You can use it like this:

from=str1 to=sth agr path1 path2 ...

Supply no paths to make it use the current directory. Note that ag, xargs, and perl need to be installed and on PATH.

HappyFace
  • 3,439
  • 2
  • 24
  • 43