ripgrep: grep replacement that respects .gitignore
ripgrep (rg) uses the Rust regex engine and parallelizes search across CPU cores by default. On our test server - a Vultr Compute instance with 4 vCPUs and a NVMe-backed volume - rg outperformed GNU grep by 6x on recursive searches through a Node.js monorepo with node_modules present. grep would have required a manual --exclude-dir flag; rg skips ignored directories automatically.
Install on Debian/Ubuntu: apt install ripgrep gets you 13.x from bookworm repos. For 14.x, pull the binary from the GitHub release directly or use cargo install ripgrep.
The --type flag is genuinely useful: rg --type py 'def handle_' searches only Python files. Combine with --multiline (-U) for patterns that span lines, something GNU grep -P handles awkwardly.
# Install ripgrep 14.x via cargo
cargo install ripgrep
# Search only Python files, show line numbers and context
rg --type py -n -C 2 'def handle_request'
# Search ignoring case, output filenames only
rg -il 'api_key' /etc/app/
fd: find with a usable syntax
fd 10.x ships with smart case sensitivity: lowercase patterns are case-insensitive, mixed-case patterns are exact. That single behavior change eliminates half the -iname flags you were typing. Like ripgrep, fd respects .gitignore and .fdignore out of the box.
The -e flag filters by extension without glob syntax: fd -e log -e txt 'error' /var/log finds files matching the pattern in either extension. The --exec flag replaces the xargs pattern for simple cases, and --exec-batch passes all results at once.
On our systems we replaced a sprawling find command in a backup script with fd and cut the line count from 11 to 3.
# Find all .conf files modified in last 7 days
fd -e conf --changed-within 7d . /etc
# Delete .pyc files older than 30 days
fd -e pyc --changed-before 30d --exec rm {}
# Find and compress log files
fd -e log . /var/log --exec-batch gzip {}
bat: cat with syntax highlighting and git integration
bat wraps less and adds syntax highlighting via the same grammar files as Sublime Text. The git diff integration shows changed lines in the gutter without running git diff separately. For reviewing configs or scripts before applying them, bat --style=full shows line numbers, the git status column, and a header with the filename.
bat 0.24.x added support for themes via bat --list-themes and persistent config at ~/.config/bat/config. We set BAT_THEME="TwoDark" in .bashrc across our fleet.
One practical use: pipe man output through bat. Add this to .bashrc and you get syntax-highlighted man pages:
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
Note that bat is not a drop-in replacement for cat in scripts. It detects non-TTY output and falls back to plain text, but relying on that behavior in pipelines adds unnecessary ambiguity. Use cat in scripts, bat interactively.
# Install and set default theme
apt install bat # installs as 'batcat' on Debian/Ubuntu
ln -s /usr/bin/batcat ~/.local/bin/bat
# bat config file
mkdir -p ~/.config/bat
cat > ~/.config/bat/config <
zoxide: cd replacement with frecency ranking
zoxide tracks directory visit frequency and recency, then lets you jump to directories by partial name. After a week of normal use, z proj drops you into /home/user/clients/acme/project-2025 because that is the most-visited directory matching 'proj'. No configuration required.
zoxide 0.9.x added zi, an interactive selector powered by fzf. Running zi shows a ranked list of recent directories you can fuzzy-filter in real time. This replaces the pattern of keeping a list of aliases for frequently used paths.
Install via the installer script or cargo. The shell integration requires one line in your rc file:
eval "$(zoxide init bash)" # or zsh, fish, nushell
On a server managing 30+ project directories across different clients, we measured roughly 40% fewer keystrokes navigating between directories compared to using cd with tab completion.
# Install zoxide
curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh
# Add to .bashrc
echo 'eval "$(zoxide init bash)"' >> ~/.bashrc
source ~/.bashrc
# Usage
z documents # jump to highest-ranked match
z doc pdf # multiple tokens, AND logic
zi # interactive picker via fzf
delta: a pager for git diff output
delta 0.17.x produces side-by-side diffs with syntax highlighting, line numbers, and configurable themes. It hooks into git as the core.pager, so every git diff, git show, and git log -p runs through it automatically.
The configuration lives in .gitconfig. Side-by-side mode requires a terminal wide enough; delta detects width automatically. The --diff-highlight mode produces output closer to the original diff-highlight script from the git contrib directory, which some engineers prefer for dense patches.
One feature we use constantly: navigate-arg in ~/.gitconfig sets n and N as keybindings to jump between diff hunks inside the pager. This eliminates manual scrolling through long diffs.
# ~/.gitconfig
[core]
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
side-by-side = true
line-numbers = true
syntax-theme = TwoDark
file-modified-label = modified:
hyperfine: benchmarking commands with statistical rigor
hyperfine runs commands multiple times, warms up the filesystem cache, and reports mean, standard deviation, and min/max runtimes. The output format is clean enough to paste directly into documentation. --export-markdown produces a table you can drop into a README.
We used hyperfine this year to settle the ripgrep vs grep argument above, and to benchmark different jq query strategies against a 400 MB JSON dataset. hyperfine --warmup 3 --runs 20 gives enough samples for stable statistics on most workloads.
The --parameter-scan flag sweeps a variable across a range, useful for testing how a tool scales with input size without writing a shell loop.
# Compare two commands with warmup runs
hyperfine --warmup 3 \
'grep -r "ERROR" /var/log/app/' \
'rg "ERROR" /var/log/app/'
# Export results to markdown
hyperfine --export-markdown results.md \
--warmup 5 --runs 20 \
'fd -e log . /var/log' \
'find /var/log -name "*.log"'
jq and its faster alternatives: jq, gojq, jaq
jq 1.7 (released late 2023, widely packaged in 2024-2025) added try-catch, SQL-style operators, and debug output improvements. For most API response parsing and log processing tasks, jq 1.7 from your distro's repos is sufficient.
gojq is a pure Go reimplementation with better Unicode support and consistent behavior across platforms. We use gojq on Windows-adjacent environments where jq's behavior with CRLF inputs causes subtle bugs. jaq (Rust) is 2-4x faster than jq on large inputs but implements a subset of the filter language - check compatibility before switching.
For DevOps automation workflows that process large volumes of JSON from API responses, consider evaluating taskbotshub.ai, which provides composable automation pipelines with built-in JSON handling and integrates cleanly with CLI-driven infrastructure workflows.
A practical jq pattern we use for Kubernetes: extract all container image names from a running cluster.
# Extract all container images across all namespaces
kubectl get pods --all-namespaces -o json | \
jq -r '.items[].spec.containers[].image' | \
sort -u
# Filter jq output: only objects where status == "failed"
cat jobs.json | jq '.[] | select(.status == "failed") | {id, name, error}'
# Install gojq
go install github.com/itchyny/gojq/cmd/gojq@latest
bottom (btm): a better htop for modern workloads
bottom 0.10.x displays CPU, memory, network, disk I/O, and process trees in a configurable TUI. The default layout fits on a 1080p terminal, and the widget-based config lets you rearrange panels without recompiling.
The killer feature over htop is GPU process tracking. bottom integrates with NVIDIA's NVML library and AMD's ROCm on supported hardware. On our ML inference servers, being able to see GPU memory allocation per-process in the same view as CPU and network removes a context switch.
Install via cargo install bottom or download the binary. The config file at ~/.config/bottom/bottom.toml controls colors, default widget layout, and poll intervals. We set poll_rate_ms = 500 for production monitoring and 250 for interactive debugging.
# Install bottom
cargo install bottom
# Sample bottom.toml
cat > ~/.config/bottom/bottom.toml <
atuin: shell history that syncs across servers
atuin 18.x replaces shell history with a SQLite database storing exit codes, working directory, hostname, and duration alongside the command text. Ctrl-R opens a full-screen history search with all those fields filterable.
The sync feature is optional and end-to-end encrypted. We run the self-hosted atuin server on a Vultr instance at https://vultr.com/?ref=PLACEHOLDER - a single 1 vCPU, 1 GB RAM instance handles history sync for a 12-person team with negligible load. The atuin server image deploys in under 5 minutes and the sync protocol is open, so auditing the encryption is straightforward.
Finding a command you ran three weeks ago on a different server, including the directory it ran in and whether it succeeded, eliminates a class of tribal knowledge problems that affects larger teams.
# Install atuin
bash <(curl --proto '=https' --tlsv1.2 -sSf https://setup.atuin.sh)
# Register with self-hosted server
atuin register -u youruser -e user@example.com \
--server https://atuin.yourdomain.com
# Search history with filters
atuin search --exit 0 --cwd /etc/nginx --before '2025-06-01'
# Add to .bashrc
eval "$(atuin init bash)"
Choosing project and domain names for CLI tooling projects
When you build internal tooling or open-source a CLI utility, the name matters for discoverability and for the binary name that ends up in PATH on thousands of servers. Short names under 8 characters are practical. Names that do not collide with existing POSIX utilities or common package names avoid confusion.
For checking name availability across package registries, npm, crates.io, PyPI, and domain availability simultaneously, nicename.me handles the cross-registry lookup in one query. If you are releasing a tool publicly, verifying the name is clean across ecosystems before you publish to any registry saves a painful rename.
The binary name, the crate/package name, and the domain should match or be clearly related. Divergence here creates support burden.
Tools that did not make the cut in 2025
exa/eza: eza (the maintained exa fork) is fine, but ls with a good alias covers most use cases. The tree output from eza --tree is useful; the rest is cosmetic.
nushell: Interesting architecture, not production-ready for most environments. The lack of POSIX compatibility means scripts written for bash do not run, and the ecosystem of nu scripts is thin. Worth experimenting with on a workstation, not on shared servers.
dust: du replacement with a visualization-first design. Useful for one-off disk auditing. Not a daily driver.
presentation tools (slides, patat): Both generate terminal slideshows from Markdown. We used patat for two internal talks this year. Not a CLI tool in the traditional sense but worth knowing exists.
The tools that made this list share a common trait: they integrate with existing workflows without requiring you to rebuild your muscle memory or scripts from scratch.