102

I am using egrep -R followed by a regular expression containing about 10 unions, so like: .jpg | .png | .gif etc. This works well, now I would like to replace all strings found with .bmp

I was thinking of something like

egrep -lR "\.jpg|\.png|\.gif" . | sed "s/some_expression/.jpg/" file_it_came_form

so the issue here is how do I do a similar union regular expression in sed and how do I tell it to save the changes to the file that it got the input from.

hardfork
  • 2,470
  • 1
  • 23
  • 43
Ori
  • 4,961
  • 10
  • 40
  • 39
  • 2
    I found this question while searching for ways to search and replace across multiple files in a directory hierarchy. For others in my situation, try [rpl](http://freshmeat.net/projects/rpl/). – titaniumdecoy May 17 '11 at 23:23
  • thank you rpl works and is really easy to remember.. just rpl old_string new_string target_files. – cesarpachon Jun 24 '15 at 14:23

6 Answers6

195

Use this command:

egrep -lRZ "\.jpg|\.png|\.gif" . \
    | xargs -0 -l sed -i -e 's/\.jpg\|\.gif\|\.png/.bmp/g'
  • egrep: find matching lines using extended regular expressions

    • -l: only list matching filenames

    • -R: search recursively through all given directories

    • -Z: use \0 as record separator

    • "\.jpg|\.png|\.gif": match one of the strings ".jpg", ".gif" or ".png"

    • .: start the search in the current directory

  • xargs: execute a command with the stdin as argument

    • -0: use \0 as record separator. This is important to match the -Z of egrep and to avoid being fooled by spaces and newlines in input filenames.

    • -l: use one line per command as parameter

  • sed: the stream editor

    • -i: replace the input file with the output without making a backup

    • -e: use the following argument as expression

    • 's/\.jpg\|\.gif\|\.png/.bmp/g': replace all occurrences of the strings ".jpg", ".gif" or ".png" with ".bmp"

toxalot
  • 11,260
  • 6
  • 35
  • 58
David Schmitt
  • 58,259
  • 26
  • 121
  • 165
  • it all works except the | in the sed part. I don't understand why though since it makes sense... the -l part of xargs was giving me errors so I took it out, could that be related? – Ori Jul 23 '09 at 06:43
  • I found that this command adds a newline to the end of all the files that it processes. – titaniumdecoy Nov 18 '11 at 21:20
  • @titanumdecoy: I wasn't able to reproduce this behaviour. what version of sed were you using and on which OS are you? – David Schmitt Nov 20 '11 at 19:46
  • 1
    @DavidSchmitt: You probably want to use `sed -r` for extended regular expressions. At that point, the pattern will match what's used in egrep, and you may want to put it in a variable for reuse. – bukzor Apr 28 '12 at 17:03
  • this command saved me hours of work copying header files out of my app for the library I made. This is awesome :) Here is the command I used egrep -lRZ "\.h$" . | xargs -0 tar -cvf headers.tar | (cp headers.tar headers; cd headers; tar xf headers.tar; ) – The Lazy Coder Jul 07 '12 at 02:02
  • Is `-e` strictly necessary for `sed` here? – Geremia Mar 09 '17 at 19:08
  • 1
    On OSX you have to provide `sed -i` with an empty file e.g.: `sed -i ''` to properly change stuff in place. – atripes Jul 07 '17 at 09:31
11

Honestly, much as I love sed for appropriate tasks, this is definitely a task for perl -- it's truly more powerful for this kind of one-liners, especially to "write it back to where it comes from" (perl's -i switch does it for you, and optionally also lets you keep the old version around e.g. with a .bak appended, just use -i.bak instead).

perl -i.bak -pe 's/\.jpg|\.png|\.gif/.jpg/

rather than intricate work in sed (if even possible there) or awk...

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 6
    sed uses -i, just like perl. – Stobor Jul 23 '09 at 06:30
  • @Stobor - I swear I've had issues where the perl operation when I fed the regex replacement string did exactly what I wanted, unlike sed, even if I gave the regex option to `sed`.. I think I either forgot some flags to sed or it had limitations. – meder omuraliev Nov 13 '12 at 21:17
11

Another way to do this

find . -name *.xml -exec sed -i "s/4.6.0-SNAPSHOT/5.0.0-SNAPSHOT/" {} \;

Some help regarding the above command

The find will do the find for you on the current directory indicated by .

-name the name of the file in my case its pom.xml can give wild cards.

-exec execute

sed stream editor

-i ignore case

s is for substitute

/4.6.0.../ String to be searched

/5.0.0.../ String to be replaced

Kevin
  • 53,822
  • 15
  • 101
  • 132
Cyril Cherian
  • 111
  • 1
  • 2
  • 1
    perhaps not as powerful - but this is a lot easier for me to understand than the accepted answer – icc97 Apr 06 '16 at 23:05
  • 1
    @icc97 - I agree that it's easier to understand, but I can't think of any reason why this would be less powerful. – dgo Oct 29 '21 at 21:29
5

I couldn't get any of the commands on this page to work for me: the sed solution added a newline to the end of all the files it processed, and the perl solution was unable to accept enough arguments from find. I found this solution which works perfectly:

find . -type f -name '*.[hm]' -print0 
    | xargs -0 perl -pi -e 's/search_regex/replacement_string/g'

This will recurse down the current directory tree and replace search_regex with replacement_string in any files ending in .h or .m.

I have also used rpl for this purpose in the past.

Community
  • 1
  • 1
titaniumdecoy
  • 18,900
  • 17
  • 96
  • 133
0

try something using a for loop

 for i in `egrep -lR "YOURSEARCH" .` ; do echo  $i; sed 's/f/k/' <$i >/tmp/`basename $i`; mv /tmp/`basename $i` $i; done

not pretty, but should do.

Don Johe
  • 989
  • 2
  • 12
  • 24
0

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.

UPDATE s|foo:/Drive_letter:|foo:/ba/baz/xyz|g

neo7
  • 654
  • 5
  • 14
  • You can use other delimiters for the sed command, and doing so makes pathnames much nicer: `sed 's|foo:/Drive_letter:|foo:/ba/baz/xyz|g'` – Kevin May 22 '13 at 13:06