grep piping into sed, replacing inline; but I want sed to print the filename and changed line. Is it possible?
Solution 1
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"
done
Solution 2
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
/etc/adduser.conf:/home
/etc/fstab:/home
/etc/fstab~:/home
/etc/libuser.conf:/home
/etc/mpd.conf:/home
/etc/mpd.conf~:/home
/etc/mtab:/home
/etc/netscsid.conf:/home
/etc/passwd:/home
/etc/passwd-:/home
/etc/sudoers:/home
/etc/sudoers.tmp~:/home
Solution 3
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.
Solution 4
Recursively replace FOO with BAR in current dir (quiet mode)
grep -rl 'FOO' | xargs sed -i 's/FOO/BAR/'
gjtorikian
Updated on September 18, 2022Comments
-
gjtorikian over 1 year
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 theblah
directory; pipe in just the filename (because of-l
) tosed
;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 fromgrep
:/Users/gjtorikian/blah/baz.cs:1:FOO /Users/gjtorikian/blah/bar.js:1:FOO
What I want is
sed
to perform the inline replace, and then show me the file and term replaced; for example:/Users/gjtorikian/blah/baz.cs:1:BAR /Users/gjtorikian/blah/bar.js:1:BAR
Is such a thing possible? If it matters, I would prefer to keep it with only
grep
/sed
. Do I have to do a secondgrep
after thesed
?-
Admin about 12 yearsI don't see a way to get the current file name in sed, so you probably will need a second grep.
-
Admin about 12 yearsAlso, you don't need the
''
, that's the default. -
Admin about 12 yearsOn OS X,
''
is not the default.
-
-
Admin over 8 years
sed -i ...
changes the filetimes even if the stream is unchanged. -
ajkal over 8 years@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 useex
(e.g.ex -sc '%s/FOO/BAR/ge|x' /Users/gjtorikian/blah
).