Getting Started: A Simple CLI Application
Let’s start with a basic command-line application that accepts arguments and prints output:
use std::env;
fn main() {
// Collect command-line arguments
let args: Vec<String> = env::args().collect();
// The first argument is the program name
println!("Program name: {}", args[0]);
// Print the remaining arguments
if args.len() > 1 {
println!("Arguments:");
for (i, arg) in args.iter().enumerate().skip(1) {
println!(" {}: {}", i, arg);
}
} else {
println!("No arguments provided");
}
}
This simple example demonstrates how to access command-line arguments using the standard library. However, for more complex applications, we’ll want to use dedicated argument parsing libraries.
Argument Parsing with clap
The clap
(Command Line Argument Parser) crate is the most popular choice for parsing command-line arguments in Rust. It provides a rich set of features for defining and parsing arguments, with helpful error messages and automatic help text generation.
Basic Usage with Builder Pattern
use clap::{App, Arg};
fn main() {
let matches = App::new("greet")
.version("1.0")
.author("Your Name <[email protected]>")
.about("A friendly greeting program")
.arg(
Arg::new("name")
.short('n')
.long("name")
.value_name("NAME")
.help("Sets the name to greet")
.takes_value(true)
.required(false),
)
.arg(
Arg::new("count")
.short('c')
.long("count")
.value_name("COUNT")
.help("Number of times to greet")
.takes_value(true)
.default_value("1"),
)
.get_matches();
// Get the values
let name = matches.value_of("name").unwrap_or("World");
let count: usize = matches.value_of("count").unwrap().parse().unwrap_or(1);
for _ in 0..count {
println!("Hello, {}!", name);
}
}
This example defines a CLI application that accepts --name
and --count
options, with short forms -n
and -c
. It automatically generates help text (accessible via --help
or -h
) and version information (via --version
or -V
).
Using derive Macros with structopt
The structopt
crate (now part of clap
v3) provides a more declarative approach using derive macros:
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "greet", about = "A friendly greeting program")]
struct Opt {
/// Sets the name to greet
#[structopt(short, long, default_value = "World")]
name: String,
/// Number of times to greet
#[structopt(short, long, default_value = "1")]
count: usize,
/// Use fancy formatting
#[structopt(short, long)]
fancy: bool,
}
fn main() {
let opt = Opt::from_args();
for _ in 0..opt.count {
if opt.fancy {
println!("✨ Hello, {}! ✨", opt.name);
} else {
println!("Hello, {}!", opt.name);
}
}
}
This approach maps command-line arguments directly to a struct, making the code more concise and maintainable.
Subcommands
Many CLI applications have multiple subcommands, each with its own set of arguments. Both clap
and structopt
support this pattern:
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "git", about = "A fictional version control system")]
enum Opt {
#[structopt(about = "Clone a repository")]
Clone {
#[structopt(help = "Repository URL")]
url: String,
#[structopt(help = "Target directory", default_value = ".")]
target: String,
},
#[structopt(about = "Commit changes")]
Commit {
#[structopt(short, long, help = "Commit message")]
message: String,
#[structopt(short, long, help = "Amend previous commit")]
amend: bool,
},
#[structopt(about = "Push changes to remote")]
Push {
#[structopt(help = "Remote name", default_value = "origin")]
remote: String,
#[structopt(help = "Branch name", default_value = "master")]
branch: String,
#[structopt(short, long, help = "Force push")]
force: bool,
},
}
fn main() {
let opt = Opt::from_args();
match opt {
Opt::Clone { url, target } => {
println!("Cloning {} into {}", url, target);
}
Opt::Commit { message, amend } => {
if amend {
println!("Amending previous commit with message: {}", message);
} else {
println!("Creating new commit with message: {}", message);
}
}
Opt::Push { remote, branch, force } => {
if force {
println!("Force pushing to {}/{}", remote, branch);
} else {
println!("Pushing to {}/{}", remote, branch);
}
}
}
}
This example defines a fictional version control system with clone
, commit
, and push
subcommands, each with its own set of arguments.
Input and Output
Command-line applications often need to read from standard input and write to standard output or error. Rust’s standard library provides several ways to do this: