🦇 batless
A fast, non-blocking cat/bat alternative
No pager, no highlighting, no waiting — just fast, predictable file viewing for scripts, pipelines, and terminals alike
🎯 Why batless?
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
# cat -n / cat -b compatible line numbering
# Structured JSON for scripts
|
# Symbol table — functions, structs, classes, with line ranges
|
Core guarantee: batless will NEVER wait for user input or block your pipeline.
🚀 Quick Start
Installation
Option A: Pre-built Binaries (Fastest)
# Linux (x86_64)
|
# macOS (Intel)
|
# macOS (Apple Silicon)
|
Option B: Via Cargo
Option C: Homebrew (macOS/Linux)
Basic Usage
# Plain text (default mode — no pager, no highlighting)
# Structured JSON output
# Symbol index — functions, structs, classes with line ranges
# Multi-file symbol index — walk a directory, one NDJSON line per file
|
# Line numbers (cat -n / cat -b compatible — requires --plain or --mode=plain)
# Limit output
# Get version info as JSON
🌟 What Makes batless Different
🏆 Feature Comparison
| 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 | ✅ | ✅ |
🚀 Core Capabilities
Non-Blocking Guarantees
- 🚫 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 Support
- 🔍 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)
Output Modes
- 📊 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)
🚫 What batless is NOT
batless has a focused design philosophy. It intentionally does NOT provide:
Features We Don't Implement (By Design)
| 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 |
Common Misconceptions
❌ "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).
When NOT to Use batless
- 👤 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.)
Our Philosophy
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.
📖 Usage Examples
Basic File Viewing
# Plain text (default — no pager, no highlighting, no colors unless piped to a terminal)
# Force no color even in a terminal
# With line numbers
# Limit output
JSON & Scripting Workflows
# JSON output for downstream processing
|
# Pretty-printed JSON
# JSON lines as {"n": N, "text": "..."} objects instead of plain strings
# CI/CD context: capture a bounded slice of a failing test file
# Machine-readable version metadata
Symbol Index
# Symbol table for one file
# Walk a directory — one compact NDJSON line per file
|
# Find every public function across a Rust crate
|
Pipeline Integration
# Use as PAGER replacement
PAGER="batless --plain"
# Process multiple files
# Combine with grep
|
# Stream stdin
|
🎨 Configuration
Language Detection
# Auto-detect (default)
# Force specific language
# List supported languages
Content Stripping
# Strip comment-only lines
# Strip blank lines
# Combine both — JSON output includes a compression_ratio field
|
Shell Completions
batless includes built-in shell completion support for bash, zsh, fish, and PowerShell.
Bash
# Generate and install completions
# Or for system-wide installation
# Then reload your shell or source the completion file
Zsh
# Generate and install completions
# Add to your ~/.zshrc (if not already present)
fpath=(/.zsh/completions )
&&
# Then reload your shell
Fish
# Generate and install completions
# Completions are automatically loaded in new fish shells
PowerShell
# 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
🔧 CLI Options
Output Modes
--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
Limiting Output
--max-lines <N>— Limit output to N lines--max-bytes <N>— Limit output to N bytes
Display Options
-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 Output Options
--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)
JSON Output Fields
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 |
Utility
--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
🏗️ Architecture
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.
🤝 Contributing
We welcome contributions! Please see:
- CONTRIBUTING.md - Contribution guidelines
- CODE_OF_CONDUCT.md - Community standards
- docs/PHILOSOPHY_AND_SCOPE.md - Project philosophy
Development Setup
# Clone repository
# Build
# Run tests
# Run with example
📊 Performance
- 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.
📜 License
MIT License - see LICENSE for details.
🔗 Links
- Documentation: docs/
- Changelog: CHANGELOG.md
- Releases: GitHub Releases
- Issues: GitHub Issues
- Crates.io: crates.io/crates/batless
🙏 Acknowledgments
- Inspired by
batby @sharkdp - Community feedback and contributions
Built for scripts, CI, and pipelines that can't afford to block