Q:

prevent sed command from writing empty file when find/replace has no match

I want to use sed to do the following:

Recursively find all .nfo files which may have a line with a unique id in them, like <id>aa123</id>. Each nfo has multiple lines (but only one id).
Replace the part between the id tags with a url with the id appended to it, like http://www.domain.com/title/aa123.
Remove all other lines so only http://www.domain.com/title/aa123 remains.

I managed to piece together something that works, however when this script does not find the id string (because not all nfo file have one), I end up with an empty nfo file. How do I prevent this?
find /home/me/Documents -name \*.nfo -exec sed -i'.bak' -r -e '/<id>(aa[0-9]+)<\/id>/!d' -e 's/<id>(aa[0-9]+)<\/id>/http:\/\/www.domain.com\/title\/\1/' {} \;

A:

You have to avoid running sed on the files that don't have a match. You can do this with an extra -exec that runs grep; if grep has a non-zero exit status, that -exec primary fails and the rest of the primaries will not be checked for that file. (Note: this is not efficient by any stretch of the imagination.)
find /home/me/Documents -name '*.nfo' \
     -exec grep '<id>aa[0-9]+</id>' {} \; \
     -exec sed -i'.bak' -r \
               -e '|<id>(aa[0-9]+)</id>|!d' \
               -e 's|<id>(aa[0-9]+)</id>|http://www.domain.com/title/\1|' {} \;

If it still doesn't work, here's an unreadable one-line version of the above:
find /home/me/Documents -name '*.nfo' -exec grep '<id>(aa[0-9]+)</id>' {} \; -exec sed -i'.bak' -r -e '|<id>(aa[0-9]+)</id>|!d' -e 's|<id>(aa[0-9]+)</id>|http://www.domain.com/title/\1|' {} \;

A:

Here is a minimally modified version of your command that handles the no-match situation separately. The advantage of this method is that you do not have to check the files twice to know if they contained the expression that you are looking for. The output of sed already contains that information and you are making backups, so:

find . -name \*.nfo \
       -exec sed -i'.bak' -r -e '/<id>(aa[0-9]+)<\/id>/!d' \
            -e 's/<id>(aa[0-9]+)<\/id>/http:\/\/www.domain.com\/title\/\1/' {} \; \
       -empty -exec mv '{}'.bak '{}' ';'

The bolded segment will check if the original file is empty and replace it with the backup that sed made. This will only happen in the no-match case, so the modified files will remain as-is.
This relies on the fact that find normally rearranges tests, but will preserve the meaning of the command in the order specified. From the man-page:

The find program reorders tests to speed up execution while preserving the overall effect; that is, predicates with side effects are not reordered relative to each other.

In this case, -exec has a side-effect, so -empty will be checked after sed runs no matter what optimizations are done. Modified files will never be empty, so mv will not run in that case.

