No pager, no highlighting, no waiting — just fast, predictable file viewing for scripts, pipelines, and terminals alike
bat is great, but it uses a pager and syntax highlighting — both of which are wrong for scripts, CI, and automated pipelines. cat never blocks, but it's plain-text only with no structure. batless sits between them: it never blocks, never pages, and adds exactly two things cat doesn't have — a --mode=json output for scripting and a --mode=index symbol table for a quick structural map of a file, without loading or parsing the whole thing yourself.
# Drop-in cat replacement, never blocks
batless large-file.log
# cat -n / cat -b compatible line numbering
batless -n --plain file.py
# Structured JSON for scripts
batless --mode=json file.py | jq '.total_lines'
# Symbol table — functions, structs, classes, with line ranges
batless --mode=index src/main.rs | jq '.symbols[] | "\(.line_start): \(.kind) \(.name)"'Core guarantee: batless will NEVER wait for user input or block your pipeline.
# Linux (x86_64)
curl -L https://github.com/docdyhr/batless/releases/latest/download/batless-x86_64-unknown-linux-gnu.tar.gz | tar xz
# macOS (Intel)
curl -L https://github.com/docdyhr/batless/releases/latest/download/batless-x86_64-apple-darwin.tar.gz | tar xz
# macOS (Apple Silicon)
curl -L https://github.com/docdyhr/batless/releases/latest/download/batless-aarch64-apple-darwin.tar.gz | tar xzcargo install batlessbrew tap docdyhr/batless
brew install batless# Plain text (default mode — no pager, no highlighting)
batless file.py
# Structured JSON output
batless --mode=json file.py
# Symbol index — functions, structs, classes with line ranges
batless --mode=index src/main.rs
# Multi-file symbol index — walk a directory, one NDJSON line per file
batless --mode=index src/ | jq -c 'select(.symbol_count > 0) | {file, symbol_count}'
# Line numbers (cat -n / cat -b compatible — requires --plain or --mode=plain)
batless -n --plain file.py
batless -b --plain file.py
# Limit output
batless --max-lines=50 large-file.py
batless --max-bytes=10000 huge-file.log
# Get version info as JSON
batless --version-json| Feature | batless |
bat |
cat |
|---|---|---|---|
| Never Blocks | ✅ Guaranteed | ❌ Uses pager | ✅ |
Symbol Index (--mode=index) |
✅ | ❌ | ❌ |
| JSON Output | ✅ First-class | ❌ | ❌ |
cat -n/cat -b compatible |
✅ | ❌ | ✅ |
| Predictable in scripts/CI | ✅ Same output every time | ❌ Pager varies by terminal | ✅ |
| Syntax Highlighting | ❌ Use bat |
✅ Rich | ❌ |
| Interactive Human Use | ❌ Not the goal | ✅ | ✅ |
- 🚫 NEVER uses a pager — no
less, nomore, no blocking - ⚡ NEVER waits for input — always streams output immediately
- 🔄 NEVER hangs in pipes — safe for
|,>, and subprocess calls - 📊 ALWAYS returns quickly — bounded reads via
--max-lines/--max-bytes
- 🔍 Language auto-detection with manual override (
--language) - 🌐 Universal plain output — works with any text-based file format
- 🗂️ Symbol extraction for Rust, Python, JavaScript, TypeScript, and a broad set of others via a lightweight heuristic extractor (functions, classes, structs, imports)
- 📊 Three output modes:
plain(default),json,index - 📏 Smart limiting by lines (
--max-lines) and/or bytes (--max-bytes) - 🎯 Predictable behavior — identical output in terminal or pipe
- ✂️ Content stripping —
--strip-comments/--strip-blank-linesfor a denser view of a file - 📦 Single <2MB binary with minimal dependencies (no bundled parser toolchains)
batless has a focused design philosophy. It intentionally does NOT provide:
| Feature | Why Not? | Use Instead |
|---|---|---|
| Pattern Search | That's grep's job |
grep -rn "pattern" path/ |
| Arbitrary Line Ranges | Beyond our scope | sed -n '10,50p' file |
| File Globbing | Shell handles this | batless *.py (shell expands) |
| Interactive Paging | We're non-blocking | Use bat or less |
| Syntax Highlighting | Adds weight for no automation benefit | Use bat |
| Git Integration | Keep it simple | Use git diff or bat |
| File Management | Not a file browser | ls, find, fd |
| Text Editing | Viewer only | Use your editor |
❌ "batless is a drop-in replacement for bat"
✅ Reality: batless is purpose-built for scripts, CI, and automation — for interactive human reading, bat is the better tool.
❌ "batless should add grep-like search"
✅ Reality: Unix philosophy — do one thing well. Use grep for searching.
❌ "batless needs more output modes"
✅ Reality: Less is more. plain, json, and index cover what people actually use — usage data backs this up (see below).
- 👤 Interactive code review: Use
bat— it has syntax highlighting and paging built for human reading - 🔍 Searching code: Use
grep,rg(ripgrep), orag(silver searcher) - 📝 Editing files: Use your favorite editor
- 📊 Complex analysis: Use language-specific tools (pylint, rust-analyzer, etc.)
Do ONE thing well: view files without ever blocking. For everything else —
highlighting, searching, editing, interactive paging — there's already a
better tool. Add features only when real usage justifies the weight.
batless's own scope decisions are usage-driven, not guesswork: --mode=plain accounts for 84% of real invocations, --mode=index for another 10% — everything else in the CLI is either free (line limits, color control) or has demonstrated real use. See docs/PHILOSOPHY_AND_SCOPE.md for the full reasoning.
# Plain text (default — no pager, no highlighting, no colors unless piped to a terminal)
batless main.rs
# Force no color even in a terminal
batless --color=never main.rs
# With line numbers
batless -n --plain main.rs
# Limit output
batless --max-lines=50 large-file.py
batless --max-bytes=10000 huge-file.log# JSON output for downstream processing
batless --mode=json src/main.rs | jq '.total_lines'
# Pretty-printed JSON
batless --mode=json --json-pretty src/main.rs
# JSON lines as {"n": N, "text": "..."} objects instead of plain strings
batless --mode=json --with-line-numbers src/main.rs
# CI/CD context: capture a bounded slice of a failing test file
batless --mode=json --max-lines=100 failing-test.rs > context.json
# Machine-readable version metadata
batless --version-json# Symbol table for one file
batless --mode=index src/main.rs
# Walk a directory — one compact NDJSON line per file
batless --mode=index src/ | jq -c 'select(.symbol_count > 0) | {file, symbol_count}'
# Find every public function across a Rust crate
find src -name "*.rs" -exec batless --mode=index {} \; \
| jq -c '.symbols[] | select(.kind=="function" and .visibility=="pub")'# Use as PAGER replacement
PAGER="batless --plain" gh pr view 42
# Process multiple files
find src -name "*.rs" -exec batless --mode=index {} \;
# Combine with grep
grep -l "TODO" src/*.py | xargs batless -n --plain
# Stream stdin
cat file.rs | batless --language=rust# Auto-detect (default)
batless file.txt
# Force specific language
batless --language=python unknown.file
# List supported languages
batless --list-languages# Strip comment-only lines
batless --strip-comments src/main.rs
# Strip blank lines
batless --strip-blank-lines src/main.rs
# Combine both — JSON output includes a compression_ratio field
batless --mode=json --strip-comments --strip-blank-lines src/main.rs | jq '.compression_ratio'batless includes built-in shell completion support for bash, zsh, fish, and PowerShell.
# Generate and install completions
batless --generate-completions bash > ~/.local/share/bash-completion/completions/batless
# Or for system-wide installation
sudo batless --generate-completions bash > /usr/share/bash-completion/completions/batless
# Then reload your shell or source the completion file
source ~/.local/share/bash-completion/completions/batless# Generate and install completions
batless --generate-completions zsh > ~/.zsh/completions/_batless
# Add to your ~/.zshrc (if not already present)
fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit
# Then reload your shell
exec zsh# Generate and install completions
batless --generate-completions fish > ~/.config/fish/completions/batless.fish
# Completions are automatically loaded in new fish shells# Generate and add to your profile
batless --generate-completions power-shell | Out-String | Invoke-Expression
# Or save to your profile for persistence
batless --generate-completions power-shell >> $PROFILE--mode <MODE>— Output mode:plain(default),json,index--plain— Plain text output (equivalent to--mode=plain, also used for PAGER compatibility)--mode=json— Structured JSON output--mode=index— Machine-readable symbol table (kind, name, line range, visibility); pass a directory to walk it and emit one NDJSON line per file
--max-lines <N>— Limit output to N lines--max-bytes <N>— Limit output to N bytes
-n, --number— Show line numbers (cat -ncompatibility; requires--plain/--mode=plain)-b, --number-nonblank— Number non-blank lines only (cat -bcompatibility; requires--plain/--mode=plain)--language <LANG>— Force specific language detection--color <MODE>— Color control:auto(default),always,never--strip-ansi— Strip ANSI escape codes from output
--json-pretty— Pretty-print JSON output--with-line-numbers— JSONlinesarray uses{"n": N, "text": "..."}objects instead of plain strings--strip-comments— Strip comment-only lines from output (addscompression_ratioto JSON output)--strip-blank-lines— Strip blank lines from output (addscompression_ratioto JSON output)
When using --mode=json, the output includes:
| Field | Type | Description |
|---|---|---|
file |
string | File path |
language |
string|null | Detected language |
lines |
array | File lines (strings, or {"n","text"} objects with --with-line-numbers) |
mode |
string | "json" |
processed_lines |
integer | Number of lines actually in lines |
total_lines |
integer | Line count in original file |
total_lines_exact |
boolean | Whether total_lines covers the full file |
total_bytes |
integer | File size in bytes |
truncated |
boolean | Whether output was truncated |
truncated_by_lines |
boolean | Whether truncation was due to --max-lines |
truncated_by_bytes |
boolean | Whether truncation was due to --max-bytes |
encoding |
string | Detected encoding |
syntax_errors |
array | Encoding/processing errors encountered, if any |
compression_ratio |
number|null | original/stripped line ratio (present only with --strip-comments/--strip-blank-lines) |
When using --mode=index, the output includes:
| Field | Type | Description |
|---|---|---|
file |
string | File path |
language |
string|null | Detected language |
mode |
string | "index" |
symbol_count |
integer | Number of symbols found |
symbols |
array | Symbol table entries |
symbols[].kind |
string | function, struct, class, impl, trait, import, etc. |
symbols[].name |
string | Symbol identifier name |
symbols[].line_start |
integer | 1-based start line |
symbols[].line_end |
integer (key omitted if unset) | 1-based end line; the field exists in the schema but the current regex/heuristic extractor never populates it, so the key is always omitted from the JSON object today, not present as null |
symbols[].signature |
string | Declaration line, trimmed |
symbols[].visibility |
string|null | pub, private, export, local, depending on language |
total_lines |
integer | Line count |
total_bytes |
integer | File size in bytes |
--list-languages— Show all supported languages--config <PATH>— Configuration file path (defaults to auto-discovery of.batlessrc/batless.toml)--debug— Enable debug mode with detailed processing information--generate-completions <SHELL>— Generate shell completions (bash,zsh,fish,power-shell)--version— Show version information--version-json— Machine-readable version metadata--help— Show detailed help information
batless is built with:
- Rust — memory safety and performance
- A small, focused dependency set — clap for argument parsing, serde/serde_json for JSON, encoding_rs for encoding detection; no bundled parser toolchains
- Bounded reads —
--max-lines/--max-bytescap memory use on large files - Modular design — clean separation between config parsing, file processing, and output formatting
See docs/ARCHITECTURE.md for technical details.
We welcome contributions! Please see:
- CONTRIBUTING.md - Contribution guidelines
- CODE_OF_CONDUCT.md - Community standards
- docs/PHILOSOPHY_AND_SCOPE.md - Project philosophy
# Clone repository
git clone https://github.com/docdyhr/batless.git
cd batless
# Build
cargo build
# Run tests
cargo test
# Run with example
cargo run -- src/main.rs- Startup time: <5ms typical on modern hardware
- Binary size: <2MB stripped (1.5MB measured on macOS arm64, down from 8.0MB before the v0.7.0 scope reduction)
- Memory usage: Bounded by
--max-lines/--max-bytes - Throughput: Limited only by disk I/O
Note: Performance varies by hardware. Benchmarks on typical developer workstation.
MIT License - see LICENSE for details.
- Documentation: docs/
- Changelog: CHANGELOG.md
- Releases: GitHub Releases
- Issues: GitHub Issues
- Crates.io: crates.io/crates/batless
- Inspired by
batby @sharkdp - Community feedback and contributions
Built for scripts, CI, and pipelines that can't afford to block