Shell Completions & Man Pages
Generate shell completions and man pages directly from your clap definitions so docs never drift.
Recipe
# Generate bash completions at build time
cargo add clap_complete --features generatoruse clap::{CommandFactory, Parser};
use clap_complete::{generate, shells::Bash};
use std::io;
#[derive(Parser)]
#[command(name = "mytool")]
struct Cli {}
fn main() {
let mut cmd = Cli::command();
generate(Bash, &mut cmd, "mytool", &mut io::stdout());
}When to reach for this: Any CLI your users install locally and expect to tab-complete like native system tools.
Working Example
// build.rs
use clap::CommandFactory;
use std::{env, fs, path::PathBuf};
fn main() {
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
let mut cmd = mytool::Cli::command();
for (shell, path) in [
(clap_complete::Shell::Bash, out.join("mytool.bash")),
(clap_complete::Shell::Zsh, out.join("_mytool")),
(clap_complete::Shell::Fish, out.join("mytool.fish")),
] {
let mut file = fs::File::create(&path).unwrap();
clap_complete::generate(shell, &mut cmd, "mytool", &mut file);
}
println!("cargo:rerun-if-changed=src/lib.rs");
}# Install bash completion (example)
sudo cp target/debug/build/mytool-*/out/mytool.bash \
/etc/bash_completion.d/mytoolWhat this demonstrates:
build.rsregenerates completions on every CLI change- Supports bash, zsh, fish, powershell, and elvish
- Single source of truth: the
Parserderive - Pack completions in release archives
Deep Dive
Man Pages with clap_mangen
use clap_mangen::Man;
let man = Man::new(Cli::command());
man.render(&mut std::io::stdout())?;Install to share/man/man1/mytool.1 in your packaging pipeline.
Distribution Checklist
| Artifact | Target path |
|---|---|
| Bash completion | /etc/bash_completion.d/mytool |
| Zsh completion | _mytool in $fpath |
| Man page | share/man/man1/mytool.1 |
Runtime vs Build-time Generation
- Build-time - Ship completions in release tarball (recommended)
- Runtime -
mytool completions bashsubcommand prints script for eval
Gotchas
- Stale completions after flag changes - Users tab-complete old flags. Fix: Regenerate in CI and bundle with releases.
- Binary rename - Completion script hardcodes the name. Fix: Pass the install name to
generate(). - Subcommand aliases missing - Only defined subcommands complete. Fix: Add
visible_aliasin clap. - Man page without section - Man expects section 1 for user commands. Fix: Use
man 1 mytoolin docs. - Nushell / custom shells - Not all shells supported. Fix: Document manual install for unsupported shells.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Hand-written completion scripts | Very custom completion logic | Standard flag/subcommand CLIs |
roff man pages by hand | Fine-tuned typography | clap-managed CLIs that change often |
--help only | Internal tools | Public developer-facing CLIs |
FAQs
How do users install completions?
Document shell-specific steps in README. Better: include a mytool completions install helper.
cargo-dist and completions?
Add generated scripts as release assets. cargo-dist can upload them alongside binaries.
Dynamic completions?
clap_complete supports Completer trait for runtime values like filenames. See clap_complete docs.
Zsh _mytool naming?
Zsh expects _<command> with no extension, placed in a directory listed in $fpath.
Fish completions?
Generate with Shell::Fish. Copy to ~/.config/fish/completions/mytool.fish.
Testing completions?
Snapshot-test generated scripts in CI. Fail the build when output changes unexpectedly.
Man page sections?
Section 1 for user commands, section 5 for config file formats.
Cross-platform man pages?
Man pages work on Linux and macOS. Windows users rely on --help and web docs.
Completions in nix/homebrew?
Package managers have hooks for completion install. Follow each formula's conventions.
Version flag in man?
Regenerate man pages each release so VERSION in the header stays current.
Related
- clap - source definitions
- Packaging & Distribution - ship with releases
- CLI Best Practices - discoverability
- CLI Basics -
--helpfundamentals
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.