Argumentos de línea de comandos en Rust
Casi todos los proyectos pequeños que vale la pena hacer para practicar (una lista de tareas, un contador de palabras) necesitan recibir datos desde la línea de comandos en lugar de tener los valores escritos en el código. En esta página verás lo básico.
Leer argumentos con std::env::args
Sección titulada «Leer argumentos con std::env::args»use std::env;
fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args);}Si compilaras esto en un programa llamado greet y lo ejecutaras así:
./greet AliciaVerías:
["greet", "Alicia"]El primer elemento siempre es el nombre del propio programa: los argumentos que escribiste empiezan en el índice 1.
Leer un argumento concreto
Sección titulada «Leer un argumento concreto»use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("Uso: greet <nombre>"); return; }
let name = &args[1]; println!("¡Hola, {name}!");}Ejecútalo con cargo run -- Alicia y obtendrás:
¡Hola, Alicia!Si lo ejecutas sin argumentos, verás el mensaje de uso en lugar de un fallo. Comprobar args.len() antes de acceder por índice evita un panic si alguien se olvida de pasar el argumento.
Convertir un argumento en un número
Sección titulada «Convertir un argumento en un número»Como todos los argumentos llegan como String, a menudo necesitarás .parse() (de la página de conversión de tipos) para convertir uno en un número:
use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("Uso: square <número>"); return; }
match args[1].parse::<i32>() { Ok(n) => println!("{}", n * n), Err(_) => println!("Eso no es un número válido"), }}Tratar una opción sencilla a mano
Sección titulada «Tratar una opción sencilla a mano»Una opción (en inglés, flag) es un argumento que activa algo, como --shout. Antes de recurrir a un crate, conviene ver cómo la tratarías tú mismo. El truco para que sea fácil de probar es poner la lógica en una función que reciba los argumentos como un slice, &[String]. Así main puede pasarle los argumentos reales, y tú puedes pasarle una lista hecha a mano mientras experimentas:
fn run(args: &[String]) { let mut shout = false; let mut names = Vec::new();
// saltamos el índice 0, el nombre del programa for arg in &args[1..] { if arg == "--shout" { shout = true; } else { names.push(arg); } }
if names.is_empty() { println!("Uso: greet [--shout] <nombre>..."); return; }
for name in names { let line = format!("¡Hola, {name}!"); if shout { println!("{}", line.to_uppercase()); } else { println!("{line}"); } }}
fn main() { // En un programa real escribirías: // let args: Vec<String> = std::env::args().collect(); // Aquí creamos la lista a mano, para que también funcione en el Playground. let args = vec![ String::from("greet"), String::from("--shout"), String::from("Ana"), String::from("Ben"), ]; run(&args);}Salida:
¡HOLA, ANA!¡HOLA, BEN!Algunas cosas en las que fijarte:
&args[1..]son todos los argumentos excepto el nombre del programa. La opción puede aparecer en cualquier posición:greet Ana --shout Bentambién funciona.- Quita
--shoutde la lista y vuelve a ejecutarlo: obtienes los saludos normales. Quita también los nombres y verás el mensaje de uso. - Como a
runno le importa de dónde viene su lista, cambiarla porstd::env::args().collect()es un cambio de una sola línea enmain.
Esto está bien para una o dos opciones. En cuanto quieras también versiones cortas (-s), valores después de una opción (--count 3) y un mensaje --help, el bucle escrito a mano crece rápido. Ese es el momento de pasarte a clap.
Para más de un par de argumentos: usa clap
Sección titulada «Para más de un par de argumentos: usa clap»Acceder a args por índice a mano se complica muy rápido en cuanto necesitas opciones (--verbose), valores opcionales o una ayuda --help útil. La solución habitual es el crate clap, que se añade con:
cargo add clap --features deriveuse clap::Parser;
#[derive(Parser)]struct Args { name: String,
#[arg(short, long, default_value_t = 1)] count: u32,}
fn main() { let args = Args::parse(); for _ in 0..args.count { println!("¡Hola, {}!", args.name); }}Con esto obtienes el análisis de argumentos, la validación y un mensaje --help que funciona, todo generado a partir de la definición del struct. Para scripts rápidos no necesitas clap, pero para cualquier herramienta que vayas a usar más de una vez te ahorra mucho trabajo manual.
Práctica
Sección titulada «Práctica»El Playground no puede pasar argumentos reales, así que los dos ejercicios empiezan con un Vec<String> hecho a mano que se parece exactamente a lo que te daría env::args().collect(). En tu equipo, cámbialo por el de verdad y prueba con cargo run -- ....
1. Lista los argumentos
Sección titulada «1. Lista los argumentos»Dado vec!["notes", "add", "comprar leche"] (como String), imprime cuántos argumentos escribió el usuario (sin contar el nombre del programa) y luego cada uno con su posición, empezando en 1.
Pista: la cantidad es args.len() - 1. Un bucle for i in 1..args.len() te da cada índice después del nombre del programa.
Ver solución
fn main() { // imagina que esto viene de std::env::args().collect() let args = vec![ String::from("notes"), String::from("add"), String::from("comprar leche"), ];
println!("Pasaste {} argumentos", args.len() - 1); for i in 1..args.len() { println!("{i}: {}", args[i]); }}Salida:
Pasaste 2 argumentos1: add2: comprar leche"comprar leche" cuenta como un solo argumento: en una línea de comandos real lo escribirías entre comillas (cargo run -- add "comprar leche"), y la shell lo pasa como una sola cadena. El índice 0 sigue siendo el nombre del programa, por eso el bucle empieza en 1.
2. Suma los números
Sección titulada «2. Suma los números»Dado vec!["sum", "4", "10", "x", "6"], suma todos los argumentos que sean un número válido e imprime un aviso por cada uno que no lo sea. Termina imprimiendo el total.
Pista: recorre &args[1..] y usa match arg.parse::<i32>(), como en Convertir un argumento en un número. Declara let mut total = 0; fuera del bucle.
Ver solución
fn main() { let args = vec![ String::from("sum"), String::from("4"), String::from("10"), String::from("x"), String::from("6"), ];
let mut total = 0; for arg in &args[1..] { match arg.parse::<i32>() { Ok(n) => total += n, Err(_) => println!("Se omite '{arg}': no es un número"), } } println!("Total: {total}");}Salida:
Se omite 'x': no es un númeroTotal: 20Todos los argumentos llegan como texto, incluso "4", así que hay que convertir cada uno. Saltarse la entrada incorrecta con un mensaje es más amable que hacer que el programa falle. También podrías detenerte en el primero incorrecto con return, si eso le va mejor a tu programa.
Resumen
Sección titulada «Resumen»std::env::args().collect()te da unVec<String>con los argumentos de línea de comandos; el índice 0 siempre es el nombre del programa.- Comprueba
args.len()antes de acceder por índice, para que un argumento que falta no provoque un panic. .parse()convierte un argumentoStringen un número, igual que en cualquier otro sitio.- Pon el tratamiento de los argumentos en una función que reciba
&[String], para poder probarla con una lista hecha a mano. - Para algo más que un par de argumentos sencillos, el crate
clapes la opción estándar y probada.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. Cuando llamas a env::args().collect(), ¿cuál es el primer elemento del Vec resultante?
2. ¿Por qué hay que comprobar args.len() antes de acceder a args por índice?
3. ¿Qué te da el crate clap que no te da acceder a args por índice a mano?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- Accepting Command Line Arguments — The Rust Book (en inglés): el comienzo del proyecto
minigrepdel libro, que lee una palabra que buscar y un nombre de archivo desde la línea de comandos. std::env::args— documentación de la biblioteca estándar (en inglés): qué devuelve exactamenteargs()y cuándo provoca un panic (con argumentos que no son Unicode válido).- Program arguments — Rust by Example (en inglés): un ejemplo corto, y una continuación sobre cómo comparar argumentos con
match. - Command Line Applications in Rust (en inglés): un minilibro gratuito sobre cómo crear herramientas CLI de verdad, desde analizar los argumentos hasta probarlas y publicarlas.
- Tutorial de derive de clap — docs.rs (en inglés): una guía paso a paso sobre opciones, valores, subcomandos y validación con
clap.