Here's my command (break intentional):

grep FOO "/Users/gjtorikian/blah" -l | xargs sed -i '' '/FOO/{s/FOO/BAR/g; w /dev/stdout

At the high-level: grep for FOO in the blah directory; pipe in just the filename (because of -l) to sed; sed performs an inline replace (-i '') and prints only the changed term to /dev/stdout.

If I were to omit the -l and pipe, I get this back from grep:


What I want is sed to perform the inline replace, and then show me the file and term replaced; for example:


Is such a thing possible? If it matters, I would prefer to keep it with only grep/sed. Do I have to do a second grep after the sed ?

  • I don't see a way to get the current file name in sed, so you probably will need a second grep. – Kevin Feb 25 '12 at 20:50
  • Also, you don't need the '', that's the default. – Kevin Feb 25 '12 at 20:51
  • 3
    On OS X, '' is not the default. – gjtorikian Feb 25 '12 at 22:57

One possibility would be to pass the output of grep to a separate sed filter.

grep -l FOO "/Users/gjtorikian/blah/"* |
{ tee /dev/fd/3 |
  xargs sed -i -e '/FOO/{' -e 's/FOO/BAR/g' -e 'w /dev/stdout'
} 3>&1 | sed 's/:FOO$/:BAR/'

You could make sed print the line number (with the = command) when it finds a match and do further postprocessing.

It would probably be clearer to use awk.

for x in "/Users/gjtorikian/blah/"*; do
  awk '
    sub(/FOO/, "BAR") {found=1; print FILENAME ":" NR ":" "BAR" >"/dev/stderr"}
    1 {print}
    END {exit(!found)}
' "$x" >"$x.tmp" && mv "$x.tmp" "$x"
| improve this answer | |

As I'm not native English speaker I probably didn't get it.

To 'grep' a directory you need '-r'. Usage of '-l' prints just filename and it stops grepping after first occurence.

# pattern=/home ; grep -l "$pattern" /etc/[a-z]* 2>/dev/null | while read line ; do echo "$line:$pattern" ; done
| improve this answer | |

I think you're making the problem too complicated by trying to do it in one go. You simply can do a

grep -H -n /Users/gjtorikian/blah | sed 's/\(^.*?:[0-9]+?:\).*FOO.*/\1BAZ/'

to get the list of files with line numbers and replacements (this should work, as long as your filenames don't contain colons, but that's a bad idea in Mac OS anyway...). Afterwards you can issue an

sed -i '' 's/FOO/BAR/g' /Users/gjtorikian/blah

No grep and xarg is needed here (you may do a "find ... | xarg" if you have a lot of files though). If you're concerned about the duplications, you can put the two lines into a script or function and do variable substitutions there.

| improve this answer | |
  • sed -i ... changes the filetimes even if the stream is unchanged. – user56041 Nov 5 '15 at 9:23
  • @jww: you're right, that's because sed is a stream editor not a file editor, so the -i option violates unix philosophy (mywiki.wooledge.org/BashFAQ/021#Using_nonstandard_tools). If you want to avoid that use ex (e.g. ex -sc '%s/FOO/BAR/ge|x' /Users/gjtorikian/blah). – David Ongaro Nov 13 '15 at 9:51

Recursively replace FOO with BAR in current dir (quiet mode)

grep -rl 'FOO' | xargs sed -i 's/FOO/BAR/'
| improve this answer | |

One liner

grep -n FOO * -R|awk -F\: '{ print $1 }'|sort|uniq|while read file; do sed -i 's/FOO/BAR/g' $file; done
| improve this answer | |

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.