windows-cli-tools

Native Windows .exe implementations of Unix CLI commands (head, tail, cat, grep, wc, tac, tee, touch, which, yes) with zero runtime dependencies. Drop them on PATH and they work in cmd, PowerShell, and any terminal.

Overview

  • Language: Rust
  • Repo: peteretelej/windows-cli-tools
  • Install: Download from Releases or cargo install --git https://github.com/peteretelej/windows-cli-tools head tail wc cat tac grep tee touch which yes
  • Status: active (10 tools)

Architecture

A Cargo workspace with one shared library crate and one binary crate per tool.

Workspace layout (crates/):

  • common - Shared library with three modules:
    • encoding.rs - UTF-16 BOM detection and transcoding via encoding_rs_io. Provides open_input(), open_stdin(), and open_input_or_stdin() that wrap file/stdin readers with automatic BOM sniffing. This is critical for Windows because PowerShell writes UTF-16LE with BOM by default
    • error.rs - err() (fatal, exits with code 1) and warn() (non-fatal stderr). Two functions, no error types
    • cli.rs - print_help() and print_version() one-liners
  • head, tail, cat, tac, wc, grep, tee, touch, which, yes - One binary crate each

Dependency map: Most tools depend only on common + lexopt. The exception is grep, which adds regex-lite for pattern matching. Some simpler tools (tee, touch, which, yes) skip common entirely and use only lexopt directly. The workspace has exactly three external runtime dependencies: lexopt (arg parsing), encoding_rs_io (BOM handling), and regex-lite (grep only).

Tool pattern: Each tool follows the same structure:

  1. A parse_args() function using lexopt (manual pattern matching on Short/Long/Value variants)
  2. A run() function that returns io::Result<()>
  3. A main() that calls run() and handles broken pipe errors
  4. Unit tests at the bottom of the file

The head crate is a good example: it defines a Mode enum (Lines/Bytes), parses args, opens input via encoding::open_input() or encoding::open_stdin(), and writes to locked stdout.

Key Design Decisions

lexopt over clap: Argument parsing uses lexopt, a zero-dependency parser that adds ~15KB to binaries vs clap’s ~300KB+. Each tool hand-matches on short/long flags, which is more code but produces tiny executables. This is the right trade-off for simple tools where the arg surface is small.

Zero runtime dependencies: The compiled .exe files link the C runtime statically (+crt-static), so there is nothing to install. No MSVC redistributable, no DLLs. This is what makes them practical for dropping onto any Windows machine, including CI runners and fresh installs.

regex-lite instead of regex: grep uses regex-lite, which trades some regex features and performance for a much smaller binary size (no Unicode tables or DFA compilation). For a CLI grep on typical inputs, this is the right trade-off.

UTF-16 BOM handling: Instead of requiring users to deal with encoding, every file-reading tool transparently detects UTF-16LE/BE BOM and transcodes to UTF-8. This is done once in common/encoding.rs and shared by all tools that read files.

Windows 7 support via Rust 1.75: The 32-bit builds target i686-pc-windows-msvc using Rust 1.75 (the last version supporting Windows 7), giving these tools reach on older systems that still need CLI utilities.

Workspace-level lint config: clippy::all = warn is set at the workspace level, so all crates share the same lint standard without per-crate configuration.

Development

cargo build --release --workspace
cargo test --workspace
cargo fmt --check
cargo clippy --workspace

# Windows 7 / 32-bit build (PowerShell)
$env:RUSTFLAGS = "-C target-feature=+crt-static"
cargo +1.75.0 build --release --workspace --target i686-pc-windows-msvc