vgrep: a pager for grep that lets you jump into the match
a user-friendly pager for grep
At a glance
- What is it?
- vgrep puts a pager in front of grep, git-grep, or ripgrep, keeps the matches in a cache, and lets you open any of them in the editor of your choice at the right line. An interactive shell then works on the indexed results, filtering by selector ranges, showing context, counting matches per directory, and starting a fresh search without leaving the shell.
- Who is it for?
- vgrep fits someone who searches a large source tree repeatedly and wants the same ergonomics from grep that they get from a code browser, in particular someone who wants to narrow a long result list and then open one hit in an editor without re-running the search.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A pager that keeps the results around
vgrep is a pager for grep, git-grep, ripgrep, and similar tools, and its distinguishing feature is that the indexed locations of a match can be opened in an editor you name. The project credits the ancient cgvg shell scripts as the inspiration and says that vgrep extends that idea with operations the scripts did not have, such as listing statistics for files and directory trees, and showing the context lines around a match. It runs on Linux, Windows, and macOS.
The mechanics are simple. A search runs through the tool underneath, the output is written to less so that a long result list is comfortable to read, and the matches are cached. The cache is what makes the rest possible: running vgrep again with no new query loads the previous results and operates on them, so the second command does not re-scan the tree.
The cache lives in a per-user directory, with a Windows path under the local application data directory and a Unix path under the home directory in a dot-cache folder, and the file name in both cases refers to the Go implementation rather than to the tool in the abstract. The pager can be bypassed with the no-less flag, which writes to standard output instead, which is the flag you want when the output is going into a pipe rather than onto a screen.
The project also respects the NO_COLOR environment variable, so the ANSI colour output can be switched off by the same convention other command line tools have adopted.
For an installation, the project lists distribution packages for Fedora, openSUSE, Arch through its AUR entry, Gentoo, and macOS through MacPorts and Homebrew, and for anything else it says to build and install manually with the make build and make install targets. The build itself is covered further down.
Which tool runs underneath, and the flags vgrep keeps for itself
vgrep is a front end rather than a search engine, and the documentation is explicit about the order in which it looks for something to run. It uses the first available search tool in this sequence: ripgrep, then git-grep when it is inside a git tree, and then grep.
That preference order is the main practical difference from running grep by hand. Inside a repository, vgrep will use git-grep, which knows about the tracked files and skips the build output that a plain grep would walk through, and if ripgrep is installed it will be used even inside a repository. Two flags override the choice: no-ripgrep keeps it away from ripgrep, and no-git forces plain grep even inside a git tree.
Everything else is passed down. All other flags and arguments are handed to the underlying grep implementation, which means the familiar options, the file globs, and the context flags keep working, and vgrep only claims a handful of its own.
Those claimed options are the ones listed in the table. The show flag takes selectors and either prints the specified matches or opens the interactive shell, the files-with-matches flag prints only the names of files that matched, and the interactive flag enters the shell. There are two output flags, no-header for dropping the column headers and no-less for writing to standard output, and the two tool-selection flags described above.
One detail is worth noticing because it changes how the output is meant to be read. When vgrep runs inside a terminal of VSCode or Goland, the format of the listed files changes so that the path and the line number are printed in a form the editor can turn into a clickable link. The same output, in a different terminal, is a plain list.
The installation example is a single call with a query in quotes, and the project includes a screenshot of that first search rather than a longer walkthrough, which is a reasonable trade for a tool whose behaviour is mostly visible in the first minute.
Opening a hit in the editor, and the two variables that control it
The editor is chosen with the EDITOR environment variable, and the example sets it and then asks vgrep to open the fourth indexed match.
$ export EDITOR=vim
$ vgrep --show 4The default is vim, and the default way of pointing at a line is the plus sign followed by the line number, which is the flag vim understands. A different editor can use a different syntax, and the EDITORLINEFLAG variable overrides it. The project gives kate as the case in point, where the editor is set to kate and the line flag becomes a minus and an l.
One constraint is documented, and it is a constraint on the workflow rather than on the tool: vgrep does not allow searching and opening files at the same time. A call that tries to do both has to be split into a search on its own and a separate show of the files.
That restriction is what makes the cache worth having. The search builds an index of matches, the show step then walks that index, and separating the two means the expensive part happens once. The alternative, a single command that searches and opens, would have to search again, and the interactive shell would have nothing to work on.
The results of the last search are cached, and that cache is also what the interactive shell operates on. Running vgrep without a new query loads the previous results, which is the behaviour that makes the shell useful: you search once, then narrow the list as many times as you like without touching the disk again.
The interactive shell, and the ten commands inside it
The interactive shell is the part of vgrep that goes past a pager, and its help output is compact enough to read in one go.
Enter a vgrep command: ?
vgrep command help: command[context lines] [selectors]
selectors: '3' (single), '1,2,6' (multi), '1-8' (range), 'all'
commands: print, show, context, tree, delete, keep, refine, files, grep, quit, ?A command can be given a number of context lines and a set of selectors, and the selector syntax has four forms: a single index, a comma separated list, a range, or the word all.
The commands divide into three groups. The printing ones narrow or widen what you see. Print limits the range of matched lines that are printed, so p 1-12,20 prints the first twelve lines and the twentieth. Context prints the lines before and after the matches, so c10 3-9 prints ten context lines around the matches numbered three to nine, and unless you say otherwise it prints five. Tree counts the matches per directory, and files counts them per file, which is the fastest way to see where the matches cluster.
The editing ones change the result set for the rest of the session. Delete removes the lines at the selected indices, keep throws everything else away, and refine keeps only the lines that match a regular expression you supply. All three are scoped to the interactive shell, so the changes are not written back to the cache.
The last group is about starting over. Grep launches a new search from inside the shell, so g -i with a phrase and a directory runs a case-insensitive search over that directory and updates the cache with the new results, and quit leaves the shell.
The same commands work from the command line through the show flag, and the example given is showing five context lines of the first ten matched lines. The interactive flag enters the shell straight away, and combining it with a show of the first match opens that match in the editor and then drops into the shell, which is the sequence the fzf integration is built to replace.
The fzf function that collapses the two steps
The project acknowledges that running vgrep twice, once to search and once to open, is a pattern worth improving, and it points at fzf for that.
The suggestion is a shell function for the bash configuration file, and it works by making the initial vgrep query the command fzf runs, then reloading the list as the query changes, and finally handing the selected line back to vgrep to open.
vgrep() {
INITIAL_QUERY="$1"
VGREP_PREFIX="vgrep --no-header "
FZF_DEFAULT_COMMAND="$VGREP_PREFIX '$INITIAL_QUERY'" \
fzf --bind "change:reload:$VGREP_PREFIX {q} || true" --ansi --phony --tac --query "$INITIAL_QUERY" \
| awk '{print $1}' | xargs -I{} -o vgrep --show {}
}Two details do the work. The no-header flag is used so that the first column of the output is the file path, which is why the last line of the pipeline can take the first field and pass it to the show command. And the fzf bind reloads the list on every change of the query, so the list you scroll through is a live search rather than a static list.
A fish version is provided as well, with the function named differently and the description saying it searches with vgrep through fzf, and the difference is that it pipes the result of the initial query through vgrep into fzf so that the selection happens among the results.
The cost of the arrangement is that it needs fzf installed next to vgrep and ripgrep, which is a larger dependency than the standalone tool. For someone who searches the same tree all day, the interactive filtering is the point, and the extra binary is a fair price.
A vendored Go build with a coverage path and a container
The repository is a Go project with the dependencies committed, and the build file makes the vendor flag part of every compile.
The build variable is defined as the go command with the vendor flag, and the build target writes the binary into a build directory with the version stamped into it through a linker flag, taking the version from a VERSION file and the commit from git.
GO_BUILD=$(GO) build -mod=vendorThe same pattern appears in three targets. The ordinary build produces a binary for the current platform, the release target builds with the position independent executable mode and stamps only the version, and the coverage target adds the coverage build tag and a counting coverage mode that instruments every package.
The coverage target exists because the project runs integration tests as a separate binary, and the codecov target uploads the profile that the integration run produces from the pipeline scripts. The linting is done by running the linter through the go tool rather than by depending on a binary, which keeps the version pinned in the module file.
The container image is minimal and is built for testing rather than for distribution. It starts from the latest Go image, takes the project name as a build argument, sets the GOPATH and the working directory, and installs three programs with the system package manager: bats, which is the shell test framework, less, and ripgrep. The last two are the tools vgrep itself drives, so the test environment has the same dependencies as a real user's machine.
The module dependencies are the usual set for a command line tool with a terminal UI: a flag parser, a line editor library for the interactive shell, a logger, a shell word splitter, a JSON iterator, a terminal package for the colour and cursor handling, a lock file library for the cache, and a tool for turning the documentation into manual pages.
The repository also carries a Go source file for its own tooling, an agent instruction file, a release configuration for the packaging pipeline, a screenshot directory, a hack directory, and test files at the top level next to the main program.
Editorial conclusion
vgrep fits someone who searches a large source tree repeatedly and wants the same ergonomics from grep that they get from a code browser, in particular someone who wants to narrow a long result list and then open one hit in an editor without re-running the search. It does not fit a scripted pipeline, because vgrep is a pager and a cache rather than a filter, and it does not fit a workflow that expects search and open to happen in one command, because the tool deliberately separates them. Verify first that ripgrep or git-grep is on the path, since vgrep prefers ripgrep and falls back to grep, that the editor variable points at a binary that accepts a line number flag, and that the cache directory is writable in the environment where you run it.
Frequently asked questions
What does vgrep do that grep does not?
It is a pager that caches the matches of a search, lets you open any indexed match in the editor named by the EDITOR variable, and offers an interactive shell for narrowing the cached results, counting matches per file or directory, showing context lines, and starting a fresh search without leaving the shell.
Which search tool does vgrep use?
The first one available, in this order: ripgrep, then git-grep when it is running inside a git tree, then grep. The no-ripgrep and no-git flags override the choice, and every other flag you pass is handed down to the underlying tool.
How do I open a vgrep match in my editor?
Set the EDITOR environment variable and run vgrep with the show flag and the number of the match to open. The default editor is vim, opened with a plus sign and the line number, and the EDITORLINEFLAG variable overrides that flag for editors that expect something different.
Why can I not search and open files in one vgrep command?
The tool does not support it, and the documentation says the call has to be split into a search and a separate show of the files. The separation is what makes the cached index useful, since the search then happens once and the show step works from the index.
How do I install vgrep on a system without a package?
Build and install it manually with the make build and make install targets. The project also lists packages for Fedora, openSUSE, Arch through the AUR, Gentoo, and macOS through MacPorts and Homebrew, and the source build vendors its dependencies and stamps the version from the VERSION file and the git commit into the binary.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/vrothberg-vgrep)Was this useful?