13

I ran the following to replace a term used in all files in the current working directory:

$ find . -type f -print0 | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

This performed the word substitution but it also created .bup files of files that never had the Ms. Johnson string.

How do I perform the substitution without creating all these unnecessary backups?

  • seems to me as a bug of sed see – Hotschke Dec 12 '13 at 17:06
  • There is probably a better approach possible using ex, and conditionally (only if the file is changed) running :!cp '%' '%.bup' before saving and exiting. Might be worth looking into. – Wildcard Mar 17 '16 at 9:15
  • I wrote a question about my idea above on the vi stack exchange. – Wildcard Mar 17 '16 at 9:33
6

You could check the files' contents to make sure that a substitution will take place when sed operates on them:

find . \
    -type f \
    -exec grep -q 'Ms. Johnson' {} \; \
    -print0 |
xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

If you want to be really clever about it, you could forego using find at all:

grep -Z -l -r 'Ms. Johnson' |
    xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'
  • 1
    I think you need a '{}' just before your \; in that -exec. – Jander Nov 21 '11 at 3:13
7

Find has an -exec operator that executes an arbitrary command. Even better, -exec is a test, so you can chain several -execs together, and if earlier commands fail, later ones won't get executed. The string {} gets replaced with the current filename, and ; marks the end of the command. You should quote both to avoid the shell interfering.

So:

find . -type f \
    -exec grep -q 'Ms. Johnson' '{}' \; \
    -exec sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g' '{}' \;
  • I've never had a problem using bare {}'s. – amphetamachine Nov 21 '11 at 3:14
  • 1
    @amphetamachine: Neither have I, but the man page suggests it. It could be a csh thing, or there might be shells (not Bash and not POSIX) who expand {} to the null string when doing brace expansion. – Jander Nov 21 '11 at 5:16
4

I looked at the man page and didn't see any way to do it directly through sed, as I'm sure you did before asking. I see several ways to work around this using grep, but I think the easiest is this:

grep -rlZ "Ms. Johnson" . | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

-r recurse
-l print filename only
-Z end names with null

-1

Seems like you need to build a list of files that match your search terms first.

search="test"
for match in $(find -type f -exec grep -l $search "{}" \;)
do sed -i'.bup' -e "/$search/ s/$search/Mrs. Melbin/g" "$match"
done
  • You forgot the . as the first argument to find. Also, as I was informed myself not too long ago, you don't actually need to quote or escape the {} – Kevin Nov 21 '11 at 3:18
  • Don't generate a list of files and perform command substitution on that. First find generates a newline-separated list of files, then the shell breaks up this list at any whitespace (not just newlines) and then the shell treats each word as a glob pattern. This only works if your file names do not contain whitespace or \[?*. Other answers on this page show how to do this correctly. – Gilles Nov 21 '11 at 23:15
  • @Kevin The . is unnecessary with find (at least find found on linux) because it's assumed when no path is passed an argument. – laebshade Nov 21 '11 at 23:48

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.