Ir al contenido

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.

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í:

Terminal window
./greet Alicia

Verías:

["greet", "Alicia"]

El primer elemento siempre es el nombre del propio programa: los argumentos que escribiste empiezan en el índice 1.

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.

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"),
}
}

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!
▶ Pruébalo tú mismo

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 Ben también funciona.
  • Quita --shout de la lista y vuelve a ejecutarlo: obtienes los saludos normales. Quita también los nombres y verás el mensaje de uso.
  • Como a run no le importa de dónde viene su lista, cambiarla por std::env::args().collect() es un cambio de una sola línea en main.

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.

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:

Terminal window
cargo add clap --features derive
use 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.

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 -- ....

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 argumentos
1: add
2: comprar leche
▶ Pruébalo tú mismo

"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.

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úmero
Total: 20
▶ Pruébalo tú mismo

Todos 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.

  • std::env::args().collect() te da un Vec<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 argumento String en 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 clap es la opción estándar y probada.

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