ov: a pager built for tables, pipes and files larger than memory
🎑Feature-rich terminal-based text viewer. It is a so-called terminal pager.
At a glance
- What is it?
- ov is a terminal pager in Go with column mode, header-aware parsing, follow and watch modes, exec mode and search that filters as it scrolls. It reads seekable files on demand and buffers pipes, and the one thing it will never do is colour your source code.
- Who is it for?
- Adopt ov when you read wide tabular output or files larger than your memory: its column mode, header-aware parsing and seekable-file reading are the reasons it exists, and the follow, watch and exec modes cover the tail-like workflows people bolt together with other tools. Do not adopt it as a general file reader for code or documents, because the README lists syntax highlighting under not supported and no configuration will add it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- 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 6, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What ov does not do: syntax highlighting
The feature list is long enough that the most useful thing in the README is the short section that follows it, titled not supported, with a single entry: ov does not support syntax highlighting for file types such as source code and markdown.
That is a deliberate scope line, and it shapes who should reach for this tool. ov is a pager for data: fixed header lines, columns, delimited text, logs, and command output. It is not a code viewer, and no amount of configuration changes that.
Everything else in the feature list points the same way. It opens files larger than memory quickly, supports fixed header lines and columns, is optimised for tabular text with column mode and customisable column colours, offers fully customisable shortcut keys and styles, handles Unicode and East Asian Width characters, and reads compressed files directly, including gzip, bzip2, zstd, lz4 and xz.
That last item has an architectural consequence. Decompressing inside the pager means a compressed file behaves like any other input, and the compressed formats are supplied by dedicated libraries rather than by shelling out to gzip or xz, so the process does not depend on those binaries being present.
The project is written in Go, requires go 1.26.0 in its module file, and its newest published version is v0.55.0 from 2026-09-29, and builds on tcell for the terminal, which is the same terminal UI layer many Go TUI tools share.
Seekable files versus pipes, and what each costs in memory
The claim that files larger than memory open quickly only holds for one of the two input cases, and the README splits them explicitly in its section on reducing memory usage: regular files, which are seekable, and other files and pipes, which are not.
For a seekable regular file the pager can seek. It reads what the screen needs and discards the rest, so a multi-gigabyte log occupies a bounded amount of memory and scrolling backwards costs a read rather than a reload. This is the case the design is built around, and it is why jumping to a line number in a huge file is feasible at all.
For a pipe there is nothing to seek to. Input arrives once and in order, so it has to be buffered, and memory grows with what you have consumed unless you use the follow behaviour designed for exactly this: reading new data as it arrives rather than accumulating all of it.
The distinction matters operationally. Piping a large rotating log into ov without follow gives you a reader that grows. Piping with follow gives you a tail that pages. Getting that wrong is the difference between a tool that handles enormous inputs and one that quietly exhausts them.
The internal packages support this design: the repository carries its own indexmap/ and biomap/ packages alongside oviewer/, which is consistent with a program that keeps indexes of line positions rather than the data itself.
Column mode, headers and getting wide tables to fit
Column mode is what separates ov from a generic pager, and the documentation treats it as a family of related settings rather than one flag.
There is a header setting for fixed header lines, a skip setting for how many lines to skip before data starts, a vertical header mode, and header column parsing, where the header row tells the pager which column is which. Column mode itself renders the text as aligned columns, and the section list adds column rainbow mode for alternating colours and column width control.
Width is the hard part, and this is where a dependency earns its place. The module includes github.com/noborus/guesswidth, a package by the same author dedicated to guessing display width, plus rivo/uniseg and clipperhouse/displaywidth for grapheme and width handling, with uax29 for Unicode segmentation. The feature list credits support for Unicode and East Asian Width characters, which is a different problem from counting bytes: a table containing Japanese or Chinese text misaligns in any pager that assumes one byte or one rune per column, and a misaligned table is unreadable rather than merely ugly.
Wrap handling sits alongside this, with word wrap mode and display markers for when a line exceeds the screen width, so you can tell whether a wrap happened or whether the text really contained a newline.
Follow, watch and exec: three ways to watch live output
ov covers the tail-like workflows in three separate modes, and the distinction between them is the useful part.
Follow mode tracks a file as it grows, like tail -f, and the README documents four variants: follow name for a specific file, follow all to follow every file in a multi-file session, follow section to follow only one section of a structured document, and sticky follow, which behaves differently when you scroll away, a detail that matters when you want to read history without losing your place in a live stream.
Watch mode is not the same thing. It monitors files for changes periodically and is built on fsnotify, so it reacts to modification events, including replacements and rotations where an incremental tail would break.
Exec mode runs a command and displays its output dynamically, which is the interactive cousin of piping. The dependencies explain the design: github.com/creack/pty for a pseudo terminal, and github.com/google/shlex for splitting a command line the way a shell would. A pty is what makes interactive programs behave, and shlex is what lets you pass a command with quoted arguments without writing a parser.
The example directories in the repository map onto these modes: exec/, header/, search/, simple/ and tail/.
Search versus filter, sections and marks
Search is listed as advanced: incremental, regex, and filter functions. The distinction between search and filter is the one that changes how you use the tool. Search moves the cursor to matches, so you still read everything. Filter removes non-matching lines, which turns the pager into a query tool over a large file.
Sections are a related idea for structured documents, letting you collapse or hide parts, with a documented example and an option to hide other sections when you want one view only. Combined with header parsing, that means you can treat a log or a delimited file as something with regions rather than as an undifferentiated stream.
Marks let you jump back to a place. The README documents marking by pattern as well as specifying a mark directly, which means a position in a multi-gigabyte file is reachable by name rather than by counting lines.
Multi-color highlighting extends the search idea visually, colouring several different words at once. It is the kind of feature that sounds minor until you are reading a log where error, warn and a specific request id all matter.
Output on exit is worth knowing about too, since it lets a pager used inside a script emit its final state rather than nothing, which is what makes pager-shaped tools composable with shell pipelines.
Installing and configuring: nine channels and four files
Distribution is unusually thorough. The README documents deb and rpm packages with curl and dpkg or rpm commands, MacPorts with port install ov, Homebrew with brew install ov, winget with winget install -e --id noborus.ov, FreeBSD pkg, an Arch AUR helper with a source build and a binary variant, nix for nixOS and both Linux and macOS, conda-forge, plain binaries, go install, and building from source.
The short forms of those commands are:
brew install ov
winget install -e --id noborus.ov
sudo port install ov
pkg install ovShell completion is generated for bash, zsh, fish and powershell, which is a small signal of how the project treats daily use.
Configuration is YAML handled by viper, and the repository ships four files that are worth reading as documentation: ov.yaml, ov.yaml.template, ov-less.yaml and ov-disable-default.yaml. The less-oriented file is the interesting one, since it points to a compatibility mode with the pager most people already have muscle memory for, and the disable-default file is the inverse, for people who want defaults removed rather than added. Style customisation extends to the underline style, the left and right status lines and the terminal title.
Building from source is two Makefile targets, with linker flags that stamp the version from git describe and the revision from a short commit hash: The build target compiles the binary with those linker flags and the install target calls sudo install into /usr/local/bin. A generate target runs go generate over the oviewer package, which is how the completion scripts are produced. The install path uses sudo install into /usr/local/bin, and a generate target runs go generate over the oviewer package, which is how the completion scripts are produced.
Where ov is the wrong tool
If you want colours on source code, stop here: the README puts syntax highlighting in the not supported list, and there is no plugin path around it. For that job a code-aware pager or an editor with a pager mode is the right tool, and ov will not grow into it.
If your workload is one small file and simple navigation, the configuration surface is a liability rather than a feature. Column modes, headers, sections, marks, multiple colour highlighting, custom key bindings, style customisation and a customisable status line are all available, and all of it is something you can misconfigure. less and more remain one keypress away and behave the way your fingers expect.
The other boundary is where the input comes from. The memory story is good for seekable files and worse for pipes, and the trade is documented rather than hidden, but it means ov is a poor fit for streaming an unbounded stream with no follow behaviour configured.
There is a portability consideration too. The compressed-format support and the terminal UI layer are well chosen, but a pager is a program you will meet on machines you do not control, including minimal containers and CI images, where installing a static binary is convenient and a full package manager is not.
On balance, ov is a good answer to one question, which is how do I read this enormous, wide, structured text file, and a poor answer to the general question of paging text.
Editorial conclusion
Adopt ov when you read wide tabular output or files larger than your memory: its column mode, header-aware parsing and seekable-file reading are the reasons it exists, and the follow, watch and exec modes cover the tail-like workflows people bolt together with other tools. Do not adopt it as a general file reader for code or documents, because the README lists syntax highlighting under not supported and no configuration will add it. Verify four things first. Install from your own package manager before falling back to a binary download, since channels exist for deb, rpm, MacPorts, Homebrew, winget, FreeBSD pkg, Arch AUR, nix and conda-forge, and a managed package is how you will get updates. If you are migrating from less, start from the ov-less.yaml configuration in the repository rather than writing your own, and check ov-disable-default.yaml for the inverse case. Decide how you will read non-seekable input, because piped output has to be buffered rather than seeked, which is the tradeoff the memory section describes and the reason to think before tailing something huge into it. And confirm your Go version if you build from source, since the module requires go 1.26.0. Start with ov on a CSV, set --header and turn on column mode.
Frequently asked questions
How do I install ov?
The README lists channels for deb, rpm, MacPorts, Homebrew, winget, FreeBSD pkg, the Arch AUR, nix and conda-forge, plus binaries, go install and building from source. For example brew install ov or winget install -e --id noborus.ov.
Does ov support syntax highlighting?
No. The README lists syntax highlighting for file types such as source code and markdown under not supported.
Can ov open files larger than available memory?
Yes for regular seekable files, which it reads on demand rather than loading whole. For pipes and other non-seekable input it buffers, so the memory-saving behaviour depends on follow mode.
What is the difference between ov follow mode and watch mode?
Follow mode tracks a growing file like tail -f, with variants for a named file, all files, one section and sticky behaviour. Watch mode monitors files for changes using fsnotify, which also catches replacements and rotations.
Does ov read compressed files?
Yes, gzip, bzip2, zstd, lz4 and xz are listed as supported, handled through Go libraries including klauspost/compress, pierrec/lz4 and ulikunitz/xz rather than by calling external binaries.