gdw2
gdw2

Reputation: 8026

Greping from within Vim (with a pipe)

I want to create a 'quickfix' list (see :help quickfix) with all files that contain lines with "abc" but not "xyz". I was hoping I could run the following vim ex command:

:grep -nHr abc * | grep -v xyz

Unfortunately, vim doesn't like the "pipe" and the command fails. What is the best way to do this from within vim?

Upvotes: 9

Views: 4562

Answers (5)

jwd
jwd

Reputation: 11144

You can set grepprg, which defines what command Vim uses to execute :grep. It can contain pipes, though it's difficult to get the escaping correct using :set. If you use :let instead, you can pass an expression. Using a string literal for that expression provides simpler escaping rules, so you can use a more readable value. Here's an example that filters out unwanted files first, then offers multiline matching with PCRE:

let &gp='find . \! \( \( -path ./node_modules -o -path ./vendor -o -name '*.tags' \) -prune \) -type f -printf \%P\\0 | sort -z | xargs -0r pcre2grep -nIM '

All the backslashes are passed to the shell as-is with the exception of those that immediately precede %, as they are consumed when preventing expansion into filenames and paths.

Of course, that would affect all your :greps but you can revert to the default command with :set gp&.

Upvotes: 0

Beelzebielsk
Beelzebielsk

Reputation: 119

It's many years later, but I found more direct alternative. cexpr command in vim. It pretty much implements the answers from @jwd and @skeept, and is a bit more flexible because it accepts a list.

Below is an example use of my own which uses pipes. I prefix all my questions in my work notes with Q: to find them quickly. I wanted to use quickfix list and vim to browse through questions, viewing the most recent first.

:cexpr system("grep -R --line-number Q: --exclude '*~' ~/_Notes/work-log/ \| sort \| tac")

Relevant excerpt from the vim help.

:cex[pr][!] {expr}  Create a quickfix list using the result of {expr} and
            jump to the first error.
            If {expr} is a String, then each new-line terminated
            line in the String is processed using the global value
            of 'errorformat' and the result is added to the
            quickfix list.
            If {expr} is a List, then each String item in the list
            is processed and added to the quickfix list.  Non
            String items in the List are ignored.
            See |:cc| for [!].
            Examples: >
                :cexpr system('grep -n xyz *')

Upvotes: 5

Kaz
Kaz

Reputation: 58667

I just figured out how to escape :grepprg to use a pipe.

In my case, my :grepprg is based on the lid utility from GNU Idutils. I want the output of the lid program to be sorted. this is because when I use a pattern, lid finds matches out of order. I want to look for a pattern like, say, pthread.*lock and step through the matches in order to follow lock nesting, and not jump around between files in a way that follows the internal order of the ID database generated by mkid, which interleaves the matches from different files.

The pipe requires double escaping. It seems that Vim ends up processing the value of grepprg as command material. If you escape the pipe just once, it is interpreted as Vim syntax.

So what I have now in my .vimrc is this:

:set grepprg=lid\ --substring\ --result=grep\ '\\<$*\\>'\ \\\|\ sort

The pipe is escaped as \\\|: an escaped backslash and an escaped pipe to produce \|, which undergoes one more round of processing to end up with |.

Based on this example, it is possible to customize your grep command to include arbitrary Unix plumbing.


If you just want to trim the results of a quicklist, or to search within the results, there is an interactive way.

After doing the :grep, open a buffer in a split window which contains the quicklist itself:

:cope[Enter]

To make the best use of this, learn about split windows and in particular, navigation between split windows using Ctrl-W Ctrl-W, and other Ctrl-W commands.

In the quickfix result window, you can "refine your search" by searching in the ordinary way, like in any buffer. Whatever line you navigate to, you can just hit Enter on that line to jump to that quicklist item in your other window. The cursor automatically goes to that window: to return to the quicklist results to search for something else, use Ctrl-W Ctrl-W.

You can also trim the quicklist results simply by deleting unwanted lines from the :cope buffer. The buffer is unmodifiable by default, so first you have to make sure your cursor is in that window and then:

 :set modifiable

Then you can do something like

 :g/xyz/d

to delete all results that contain xyz. Only the lines which remain are now part of the quickfix list.

Upvotes: 2

skeept
skeept

Reputation: 12423

you can do it in two steps:

:!(grep -nHr abc * | grep -v xyz >| qf.txt)
:cfile qf.txt | copen

if you change frequently of patterns yo probably can use a function to wrap this the following is not perfect, but works:

fu! Mygrep(pat1, pat2)
  let cmd = "silent !(grep -nHr " . a:pat1 . " * | grep -v " . a:pat2 . " >| qf.txt)"
  silent exec cmd
  cfile qf.txt
  copen 
endfunction

and then call it using:

:call Mygrep("abc", "xyz")

it seems to work for me but I also get an error message "trailin charathers" (you may need to type to clear the screen).

Upvotes: 1

jwd
jwd

Reputation: 11144

For some reason I can't leave this one alone!

How about use :!grep ... > filename followed by :cf filename, which will open the output as a quickfix list.

Upvotes: 4

Related Questions