Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. 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

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 ?

share|improve this question
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
On OS X, '' is not the default. – GJTorikian Feb 25 '12 at 22:57
up vote 2 down vote accepted

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"
share|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
share|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.

share|improve this answer
sed -i ... changes the filetimes even if the stream is unchanged. – jww 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

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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