HOWTO: Performing global search and replace across files on your Linux machine
Posted : July 6, 2004 at 1:40 pm [America/Los_Angeles]
Version History:
Date: 07/06/2004
Version: 0.3
Notes: Edited the entry to add a version showing how to parse files in sub-folders as well. Thanks to Wei (see comments below) for pointing this out.
Version History:
Date: 07/06/2004
Version: 0.2
Notes: Edited the entry to seperate out two solutions - Bash/Sed based and Perl based. Thanks to MS (see comments below) for pointing this out.
Date: 07/06/2004
Version: 0.1
Notes: Original post
Was assigned a simple task this morning:
... <a href="http://indrayam.com">Back to Home</a> </body> </html>
There are lots of files in a directory that have a pattern similar to the one shown above. My assignment was to replace the URL in the href entries with a new one.
Sounds like a fairly common and simple problem. However, I soon realized that I did not have a canned utility or shell script that I could quickly use. After some googling around, here’s what I came up with:
Bash/Sed version:
#!/bin/bash for i in $( ls *.html ); do sed "s/\"http://indrayam.com\"/\"http://indrayam.com/it/\"/g" $i > $i.new mv -f $i.new $i done
If you want to peruse through content a few levels deep, try this:
#!/bin/bash for i in $( find . -name '*.html' ); do sed "s/\"http://indrayam.com\"/\"http://indrayam.com/it/\"/g" $i > $i.new mv -f $i.new $i done
Here are a few pointers, in case you do decide on using the script:
- For starters, restrict the $(ls *.<file-extension>) section of the shell script to a single file by replacing it with $(ls <filename>). In other words, test the script out on a single file first.
- Always run the “double-whammy” test - Run the shell script once and then run the script again without any modifications!. If the targetted file looks ok after this double execution, you have wisely selected your initial-pattern and new-pattern in sed’s s/<initial-pattern>/<new-pattern>/g expression.
For example, let’s say the sed expression in the shell script shown above looked something like this instead:
sed "s/http://indrayam.com/http://indrayam.com/it//g" $i > $i.new
Do you see a problem here? Since our initial-pattern is now http://indrayam.com (as opposed to “http://indrayam.com” in the shell script above), executing the script against the same file twice will result in href looking something like:
<a href="http://indrayam.com/it//it/">Back to Home</a>
Of course, this double-whammy problem will bite you primarily if your initial-pattern is a subset of the new-pattern
- Only after you’ve tested for these two things should you consider opening up the spigot by resetting $(ls <filename>) to $(ls *.<file-extension>)
Note:
If you’re looking for a backup of the original file, you might want to tweak the shell script above as follows:
#!/bin/bash for i in $( ls *.html ); do cp $i $i.bak sed "s/\"http://indrayam.com\"/\"http://indrayam.com/it/\"/g" $i > $i.new mv -f $i.new $i done
Perl One-liner:
If you like Perl, here’s a really, really neat one-liner:
(unix prompt)>perl -i.bak -p -e 's#"http://indrayam.com"#"http://indrayam.com/it/"#ig' *.html
If you want to peruse through content a few levels deep, try this:
(unix prompt)>perl -i.bak -p -e 's#"http://indrayam.com"#"http://indrayam.com/it/"#ig' {.,*,*/*,*/*/*}/*.html
Nice!
For more information, check out this site Thanks to MS (see comments below) for pointing this out.
Note:
The last perl one-liner example shown above will parse files in current dir and all subdirs three levels deep. I am not sure what would be the easiest way to parse files at an undefined depth level using perl one-liner without making it insanely cryptic. Any ideas?
- Anand
Category: How do I?
11 Comments
What’s the difference between “for i in $( ls *.html ); …” and “for i in *.html;”? Wouldn’t that work too?
Posted by: Janek Schwarz at July 6, 2004 @ 2:47 pm
try searching on “perl -eip” and look at pages like:
http://www.rice.edu/web/perl-edit.html
It assumes you ahve perl but well I assume that most unix installs do these days.
Posted by: MS at July 6, 2004 @ 2:49 pm
Another utility you might find quite useful is supersed (http://queen.rett.polimi.it/~paolob/seders/ssed/). With its -R option, it allows you to use Perl-like regular expressions. Not as quirky as sed’s
Posted by: Maik at July 6, 2004 @ 3:03 pm
Whoops, make that http://sed.sourceforge.net/grabbag/ssed/ — I guess Gentoo has an outdated URL there :-/
Posted by: Maik at July 6, 2004 @ 3:05 pm
MS:
I checked out the Perl site at Rice. Super stuff and thank you for sharing! Considering that I am a huge Perl fan myself, I could not have asked for anything better.
Now, I just have to remember the command-line arguments..;-)
Thanks again!
Posted by: Anand Sharma at July 6, 2004 @ 3:13 pm
Janek:
Clarity is one reason I would go with a $() construct. But then again, that’s just me..:-)
- Anand
Posted by: Anand Sharma at July 6, 2004 @ 3:14 pm
Thanks Maik. Will check it out.
- Anand
Posted by: Anand Sharma at July 6, 2004 @ 3:16 pm
Heck! This article is a day late! I was looking yesterday some pointers for sed (I am still weak in regex)…
Finally, I just got down a few hours hacking searchnreplace.java this morning!
Posted by: hishammk at July 6, 2004 @ 7:41 pm
To recursively go through all directories and its sub-directories, you probably will do something like:
for i in ( find . -name ‘*.html’ ); do
…
done
Posted by: wei at July 6, 2004 @ 8:04 pm
Very good point, Wei. I will make sure I add that to the entry.
- Anand
Posted by: Anand Sharma at July 6, 2004 @ 8:27 pm
#!/bin/bash
replace “domain.com” “new-domain.com” — /path/to/your/files/*.ext
Might help
Thanks all
Posted by: Mohamed at January 3, 2007 @ 9:20 am