Passing regular files only to `sed -i`

filessedwildcards

I am using GNU sed 4.2.2 and after searching cannot find out the reason sed behaves oddly in some situations:

I have a directory with the following:

foofile.txt
barfile.txt
bazfile.txt
config/

Case 1

sed -i 's/foo/bar/g' *.txt

This works as I expect. It replaces all the "foo"s with "bar"s in the three regular files.

Case 2

sed -i 's/foo/bar/g' *
sed: couldn't edit config: not a regular file

It replaces "foo" with "bar" in barfile.txt and bazfile.txt, but not in foofile.txt. I assume it goes through the list of files expanded from the * alphabetically, and when it hits config/ it errors and exits. Is there a way to have sed ignore errors and continue processing files?

Case 3

for file in $(find . -maxdepth 1 -type f); do sed -i 's/foo/bar/g' <"$file"; done
sed: no input files
sed: no input files
sed: no input files

Could someone please explain why sed does this? Why does it say there's no input file when it's being given one?

I know that I can use the following but I'm asking why sed acts this way, not how do I solve this one use case.

find . -maxdepth 1 -type f -exec sed -i 's/foo/bar/g' {} \;

Best Answer

It's normal behavior. In both cases sed exits with error code 4... per info sed:

4
     An I/O error, or a serious processing error during runtime,
     GNU 'sed' aborted immediately.

and in both cases the messages are self-explanatory. Not sure what's unclear but for the record: the first time it errors out because it cannot edit a directory and the second time it complains because it cannot edit stdin in-place, it needs a file (i.e. remove that redirect before $file)
The proper way to do this with find is, as you noted, via -exec ...
With globs, you'll have to use a loop and test if input is a regular file before running sed. Or, if you're a zsh user, you can simply do:

sed -i 's/foo/bar/g' *(.)
Related Question