· Zen HuiFer · Learn · 3 min read
Quickly build powerful command-line tools using Rust
Command line tools (CLI) are indispensable tools for developers and system administrators. Rust is renowned for its performance and security, making it an excellent choice for building robust and efficient CLI applications. This guide will take you step by step through creating a command-line tool using Rust and fully utilizing the latest features of Rust 1.70+.
Quickly build powerful command-line tools using Rust
Command line tools (CLI) are indispensable tools for developers and system administrators. Rust is renowned for its performance and security, making it an excellent choice for building robust and efficient CLI applications. This guide will take you step by step through creating a command-line tool using Rust and fully utilizing the latest features of Rust 1.70+.
Why choose Rust to build CLI tools?
performance Rust compiles to native code without the need for runtime or garbage collector.
Security Memory security assurance can prevent common errors.
ecosystem Rich libraries and tools.
Concurrent Excellent support for asynchronous programming.
Development environment setup
Ensure that the latest version of Rust is installed. Rustup is a recommended tool for managing Rust versions.
Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Update Rust
rustup update
Create a new Rust project
Create a new Rust project using Cargo (Rust’s package manager).
cargo new my_cli_tool
cd my_cli_tool
useclap
Analyze command-line parameters
clap
It is a powerful crate used for parsing command-line parameters and subcommands.
takeclap
Add to Dependency
stayCargo.toml
Add:
[dependencies]
clap = { version = "4.3.10", features = ["derive"] }
Write parameter parser
staysrc/main.rs
In the middle:
use clap::{Arg, Command};fn main() {
let matches = Command::new("my_cli_tool")
.version("1.0")
.author("Your Name <you@example.com>")
.about("Does awesome things")
.arg(
Arg::new("input")
.about("Input file")
.required(true)
.index(1),
)
.arg(
Arg::new("verbose")
.short('v')
.about("Increases verbosity")
.multiple_occurrences(true),
)
.get_matches(); // Access arguments
let input = matches.value_of("input").unwrap();
let verbosity = matches.occurrences_of("verbose"); println!("Input file: {}", input);
println!("Verbosity level: {}", verbosity);
}
Run the application program
cargo run -- input.txt -vvv
Output:
Input file: input.txt
Verbosity level: 3
Implement core functions
Let’s add some features to the CLI tool. Assuming we are creating a tool to count the number of lines, words, and characters in a text file (similar towc
)。
read file
staymain.rs
Add the following code:
use std::fs::File;
use std::io::{self, BufRead, BufReader};fn main() {
// ... (previous code) // Open the file
let file = File::open(input).expect("Could not open file");
let reader = BufReader::new(file); // Initialize counters
let mut lines = 0;
let mut words = 0;
let mut bytes = 0; for line in reader.lines() {
let line = line.expect("Could not read line");
lines += 1;
words += line.split_whitespace().count();
bytes += line.len();
} println!("Lines: {}", lines);
println!("Words: {}", words);
println!("Bytes: {}", bytes);
}
Run the application program
Create a file namedinput.txt
Example file:
echo "Hello world!" > input.txt
echo "This is a test file." >> input.txt
Run this tool:
cargo run -- input.txt
Output:
Input file: input.txt
Verbosity level: 0
Lines: 2
Words: 6
Bytes: 33
Add sub commands and advanced features
Subcommands allow your CLI tool to perform different operations.
Update using sub commandsclap
Parser
modifymain.rs
:
use clap::{Arg, Command};fn main() {
let matches = Command::new("my_cli_tool")
.version("1.0")
.author("Your Name <you@example.com>")
.about("Does awesome things")
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("count")
.about("Counts characters, words, or lines")
.arg(
Arg::new("chars")
.short('c')
.long("chars")
.about("Count characters"),
)
.arg(
Arg::new("words")
.short('w')
.long("words")
.about("Count words"),
)
.arg(
Arg::new("lines")
.short('l')
.long("lines")
.about("Count lines"),
)
.arg(
Arg::new("input")
.about("Input file")
.required(true)
.index(1),
),
)
.get_matches(); match matches.subcommand() {
Some(("count", sub_m)) => {
let input = sub_m.value_of("input").unwrap();
let count_chars = sub_m.is_present("chars");
let count_words = sub_m.is_present("words");
let count_lines = sub_m.is_present("lines"); // Call function to perform counting
perform_counting(input, count_chars, count_words, count_lines);
}
_ => unreachable!("Exhausted list of subcommands"),
}
}fn perform_counting(input: &str, chars: bool, words: bool, lines: bool) {
// ... (implement counting logic)
}
realizationperform_counting
fn perform_counting(input: &str, chars: bool, words: bool, lines: bool) {
let file = File::open(input).expect("Could not open file");
let reader = BufReader::new(file); let mut line_count = 0;
let mut word_count = 0;
let mut char_count = 0; for line in reader.lines() {
let line = line.expect("Could not read line");
if lines {
line_count += 1;
}
if words {
word_count += line.split_whitespace().count();
}
if chars {
char_count += line.len();
}
} if lines {
println!("Lines: {}", line_count);
}
if words {
println!("Words: {}", word_count);
}
if chars {
println!("Characters: {}", char_count);
}
}
Use subcommands to run tools
cargo run -- count -l -w input.txt
Output:
Lines: 2
Words: 6
Error handling and logging
For CLI tools, robust error handling and logging are crucial.
useanyhow
Perform error handling
stayCargo.toml
Add inanyhow
:
[dependencies]
anyhow = "1.0"
Update function to returnResult
:
use anyhow::{Context, Result};fn main() -> Result<()> {
// ... (previous code) Ok(())
}fn perform_counting(input: &str, chars: bool, words: bool, lines: bool) -> Result<()> {
let file = File::open(input).with_context(|| format!("Could not open file '{}'", input))?;
// ... (rest of the code) Ok(())
}
useenv_logger
Add log records
stayCargo.toml
Add inenv_logger
andlog
:
[dependencies]
log = "0.4"
env_logger = "0.10"
staymain
Initialize the logger:
use log::{debug, error, info, trace, warn};fn main() -> Result<()> {
env_logger::init(); // ... (rest of the code)
}
Using log macros in code:
fn perform_counting(input: &str, chars: bool, words: bool, lines: bool) -> Result<()> {
info!("Starting counting for file: {}", input);
// ... (rest of the code)
Ok(())
}
Set log level when running applications:
RUST_LOG=info cargo run -- count -l -w input.txt
Enhance user experience
usecolored
Add color output
stayCargo.toml
Add incolored
:
[dependencies]
colored = "2.0"
Use in outputcolored
:
use colored::*;println!("Lines: {}", line_count.to_string().green());
println!("Words: {}", word_count.to_string().yellow());
println!("Characters: {}", char_count.to_string().blue());
useindicatif
Add progress bar
stayCargo.toml
Add inindicatif
:
[dependencies]
indicatif = "0.17"
Use progress bar:
use indicatif::ProgressBar;fn perform_counting(input: &str, chars: bool, words: bool, lines: bool) -> Result<()> {
let file = File::open(input).with_context(|| format!("Could not open file '{}'", input))?;
let metadata = file.metadata()?;
let total_size = metadata.len(); let reader = BufReader::new(file);
let pb = ProgressBar::new(total_size); // ... (read the file and update pb) pb.finish_with_message("Done");
Ok(())
}
Build and distribute your CLI tools
Build and publish binary files
cargo build --release
The binary file will be located attarget/release/my_cli_tool
。
usecross
Perform cross compilation
installcross
:
cargo install cross
Build for different goals:
cross build --target x86_64-unknown-linux-musl --release
usecargo-bundle
pack
For macOS applications or Windows installers, please consider usingcargo-bundle
。
Publish to Crate.io
stayCargo.toml
New data from China and Singapore:
[package]
name = "my_cli_tool"
version = "1.0.0"
authors = ["Your Name <you@example.com>"]
description = "A CLI tool for counting lines, words, and characters."
homepage = "https://github.com/yourusername/my_cli_tool"
repository = "https://github.com/yourusername/my_cli_tool"
license = "MIT"
Login and publish:
cargo login
cargo publish
summary
You have now created a feature rich command-line tool using Rust, including parameter parsing, subcommands, error handling, logging, as well as color output and progress bars to enhance user experience.
Key points:
Rust’s performance and security make it an ideal choice for CLI tools.
clap
Crate simplifies parameter parsing and sub command management.use
anyhow
Perform error handling and usageenv_logger
Logging improves robustness.use
colored
andindicatif
Enhancing UX makes your tools easier to use.Using Cargo and its related tools makes it easy to build and distribute your tools.