6

Every once in a while there's an answer (or a comment) that suggests using grep's -v and -l switches together instead of the -L (especially when the latter isn't available). The authors seem to believe they are equivalent.

So, are they interchangeable? IOW, does grep -v -l produce the same result as grep -L?

10

No, they're not equivalent and as such the results will almost always1 be different.
Let's see what each of those switches does:

‘-L’
‘--files-without-match’
     Suppress normal output; instead print the name of each input file
     from which no output would normally have been printed.

This should be simple:
grep -L 'pattern' ./*
prints the name of each file that does not contain any line matching 'pattern'.

‘-v’
‘--invert-match’
     Invert the sense of matching, to select non-matching lines.

This should be very simple too:
grep -v 'pattern' ./*
prints all lines not matching 'pattern' from each file.

‘-l’
‘--files-with-matches’
     Suppress normal output; instead print the name of each input file
     from which output would normally have been printed.

Again, simple:
grep -l 'pattern' ./*
prints the name of each file that contains at least one line matching 'pattern'.

Now, what happens when combining -v and -l ? It's not much different from grep -l, except that it selects the non-matching lines hence
grep -vl 'pattern' ./*
prints the name of each file that contains at least one line not matching 'pattern'.


So, note the difference:

grep -L prints the file name only if there is no line matching 'pattern'
grep -vl prints the file name only if there is at least one line not matching 'pattern'


1:
based on the above, it's easy to see when could these two commands produce the same output:
-either when the file is not empty and all lines match the pattern - in which case there will be no output
-or when the file is not empty and no line matches the pattern - in which case it will be listed


Moral of the story: never use grep -vl to emulate grep -L.
If your grep does not support -L, this is the proper way to emulate it.

  • These are not the only cases where the two commands produce the same output. It can happen by coincidence. A useful case is that they are equivalent when all files contain exactly one line. – Gilles Jan 24 '17 at 23:35
  • It is enough for each matching file to have only matching lines or only non-matching lines, they don't have to all fall under the same case. Also, in the “only matching lines” case, there has to be at least one line. – Gilles Jan 25 '17 at 0:06
3

As an extra note, on systems whose grep doesn't support the -L option (a GNU extension), using -vl wouldn't help as @don_crissti has shown, you'd need to resort to a shell loop. The equivalent of

grep -L pattern ./*

would be:

for file in ./*; do
  {
     grep -q pattern || printf '%s\n' "$file"
  } < "$file"
done

You'll notice that the redirection is done on the whole {...} instead of just grep as we don't want the name of the file to be printed if it can't be opened.

For the equivalent of grep -ZL pattern ./* (which you need if you want to be able to post-process that output reliably), replace %s\n with %s\0 above.

For the equivalent of GNU's grep -rL pattern ., you can do:

find . -type f ! -exec grep -q pattern {} \; -print

Though it would also print the names of the files that are not readable (the -readable find predicate could be used, but it's also a GNU extension, so chances are that if your grep doesn't support -L, your find won't support -readable either).

With zsh:

for f (./**/*(DN.:) {{grep -q pattern || print -r $f}<$f}

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.