Bash – grep -v unexpected behaviour

bashgreprm

Let's say I have a simple stupid script that removes files by ending, looking like this:

rm *.uvw *.xyz

The script, or rm, to be precise, writes messages on stderr if it cannot find at least one file with the specified ending.

Now let's say the script is a bit bigger and does a bit more with a bit more file types and I'm not interested in which file types exist and which don't, but the complains about non-existent file types obstruct the rest of the output and error messages I am more interested in, so I want to filter the output:

rm *.uvw *.xyz 2>&1 | grep -v "No such file or directory"

This works fine for the most part, but it removes the message part of interactive dialogs, which for example ask, if a write-protected file should be deleted, so I get prompted without the according message.

I do not understand this behaviour and could not find any related information. Can someone explain this?

Best Answer

The problem

When rm prompts the use for input, it does not put a newline at the end of the prompt:

$ rm *.uvw *.xyz
rm: remove write-protected regular empty file 'a.xyz'?

grep is line-based. It can only process complete lines. It cannot tell whether the line should be printed until the line is complete. Thus, standard utilities for dealing with buffering, such as stdbuf, cannot help.

The solution

Use nullglob and remove the missing files messages.

Without nullglob, the messages you don't want appear:

$ rm *.uvw *.xyz
rm: cannot remove '*.uvw': No such file or directory
rm: remove write-protected regular empty file 'a.xyz'? n

With it, the "No such file or directory" message is suppressed:

$ shopt -s nullglob
$ rm *.uvw *.xyz
rm: remove write-protected regular empty file 'a.xyz'? n

Refinement

If there is no file at all that matches either glob, then a different error message appears:

$ shopt -s nullglob
$ rm *.uvw *.xyz
rm: missing operand
Try 'rm --help' for more information.

A simple way to avoid this is to make sure that at least one such file exists:

shopt -s nullglob
[ -e "deleteme.xyz" ] ||touch deleteme.xyz
rm *.uvw *.xyz

Since deleteme.xyz is going to be erased anyway, there is no harm in touching it before we run rm.

Related Question