761 - Rust-script. El fin de los scripts en Bash

761 - Rust-script. El fin de los scripts en Bash

Descubre cómo Rust Script sustituye a tus scripts de Bash complejos con la potencia y seguridad de Rust sin usar Cargo. ¡Optimiza tu Linux!.

1:25
-3:15

A pesar de que fish se ha convertido en mi shell hacer ya algún tiempo, y que programar en fish, me resulta mas intuitivo que en bash, sigo teniendo que lidiar con scripts de bash para automatizar tareas en Linux. Sin embargo, los scripts de bash tienen sus limitaciones, especialmente cuando se vuelven largos y complejos. Pero no solo se trata de salir del círculo de bash para scripts complejos. La cuestión es cada vez me encuentro mas cómodo en Rust, y quiero sacarle mas punta. Así, que he decidido darle una segunda vuelta a rust-script que permite ejecutar Rust como un lenguaje de scripting, eliminando la complejidad de Cargo para tareas rápidas.

Rust-script. El fin de los scripts en Bash

Sobre la complejidad de los scripts de Bash

Si has programado en Bash, ya te habrás dado cuenta que a medida que los scripts crecen en tamaño y complejidad, se vuelven difíciles de mantener. La gestión de errores es limitada, el manejo de datos estructurados como JSON o YAML es complicado, y la falta de tipado puede llevar a errores sutiles que son difíciles de depurar.

Lo cierto es que ya hace tiempo que cuando la cosa se complicaba, tiraba de Python y últimanente de Rust, pero el tener que crear un proyecto completo con cargo new para tareas rápidas me echaba para atrás. Y justo, aquí es donde entra en juego rust-script.

Sobre rust-script

La gran ventaja de rust-script es que permite escribir scripts en Rust sin la necesidad de configurar un proyecto completo con Cargo, que es precisamente lo que me tiraba para atrás. Esto significa que puedes aprovechar la potencia y seguridad de Rust para tareas rápidas y automatizaciones, sin la sobrecarga de la configuración tradicional.

Así, simplemente si quieres sacar uno hola mundo con rust-script, puedes crear un archivo hola_mundo.rs con el siguiente contenido:

#!/usr/bin/env rust-script
println!("Hola, mundo desde Rust-script!");

Ya está!. Es posible que ahora pienses que tiene que compilarse y que eso lleva tiempo. Cierto, la primera vez que lo ejecutes, pero las siguientes veces será instantáneo gracias a la caché que utiliza rust-script.

Como digo, la primera vez compila, con lo que tarda un poco, aunque si ejecutas ese hola mundo, verás que ni se nota. Y las siguientes veces, será instantáneo. Esto es así, porque tiene esa caché en ~/.cache/rust-script. La gracia es que puedes ir tu mismo y chafardear por ahí, para ver que es lo que hay y lo que no. De echo, si te fijas, verás que tus scripts aparecen allí con un hash como nombre de archivo. Ese hash es el resultado de combinar el contenido del script y sus dependencias.

Por otro lado, se comporta como cualquier lenguaje de scripting, con lo que puedes usar el shebang para ejecutarlo directamente desde la línea de comandos. Simplemente le tienes que dar permisos de ejecución con chmod +x hola_mundo.rs y luego ejecutarlo con ./hola_mundo.rs.

Instalación

Para instalar rust-script, en el caso de Arch Linux y derivados, puedes usar el siguiente comando,

sudo pacman -S rust-script

En el caso de que tu distribución no lo tenga en sus repositorios, puedes instalarlo usando cargo con el siguiente comando,

cargo install rust-script

El problema de las dependencias

Seguro que ya te has preguntado, ¿y qué pasa si quiero usar crates externos en mi script?. Pues bien, rust-script tiene una solución muy elegante para esto. Puedes especificar las dependencias directamente en el script usando comentarios especiales. ¿Cómo?. Pues así:

#!/usr/bin/env rust-script
// cargo-deps: walkdir = "2.5"

use walkdir::WalkDir;
use std::env;

fn main() {
    // Obtenemos el directorio de los argumentos o usamos el actual por defecto
    let path = env::args().nth(1).unwrap_or_else(|| ".".to_string());

    println!("Escaneando archivos .rs en: {}", path);

    // Iteramos por el directorio de forma recursiva
    for entry in WalkDir::new(path)
        .into_iter()
        .filter_map(|e| e.ok()) // Ignoramos errores de permisos, etc.
    {
        let file_name = entry.file_name().to_string_lossy();

        // Filtramos para mostrar solo archivos con extensión .rs
        if entry.file_type().is_file() && file_name.ends_with(".rs") {
            let metadata = entry.metadata().unwrap();
            println!(
                "Fichero: {:<30} | Tamaño: {} bytes",
                file_name,
                metadata.len()
            );
        }
    }
}

En el caso de que la cosa se complique un poco con el tema de las dependencias lo puedes hacer de la siguiente forma,

#!/usr/bin/env rust-script
//! ``cargo
//! [dependencies]
//! reqwest = { version = "0.11", features = ["json"] }
//! tokio = { version = "1", features = ["full"] }
//! serde = { version = "1.0", features = ["derive"] }
//! serde_json = "1.0"
//! ``


#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::get("https://atareao.es") .await?;
    let body = response.text().await?;
    println!("{}", body);
    let json: serde_json::Value = serde_json::from_str(&body)?;
    println!("Response JSON: {}", json);
    Ok(())
}

JSON, API REST y más

Otro de los problemas mas habituales que te encontrarás trabajando con Bash, es lidiar con respuestas JSON de APIs REST. Con rust-script, puedes usar crates como reqwest para hacer solicitudes HTTP y serde para deserializar JSON de manera sencilla y segura. En el caso de Bash, tienes que hacer uso de herramientas externas como jq, lo que añade complejidad y posibles puntos de fallo.

Por ejemplo,

#!/usr/bin/env rust-script
//! ``cargo
//! [dependencies]
//! serde = { version = "1.0", features = ["derive"] }
//! serde_json = "1.0"
//! ``

use serde::{Serialize, Deserialize};

// Definimos la estructura de nuestros datos
#[derive(Serialize, Deserialize, Debug)]
struct Proyecto {
    nombre: String,
    url: String,
    tecnologias: Vec<String>,
    activo: bool,
}

fn main() {
    let datos_json = r#"
        {
            "nombre": "atareao.es",
            "url": "https://atareao.es",
            "tecnologias": ["Rust", "Linux", "Docker"],
            "activo": true
        }
    "#;

    let proyecto: Proyecto = serde_json::from_str(datos_json).expect("Error al parsear JSON");

    println!("--- Datos recuperados ---");
    println!("Proyecto: {}", proyecto.nombre);
    println!("Tecnología principal: {}", proyecto.tecnologias[0]);

    let json_salida = serde_json::to_string_pretty(&proyecto).unwrap();

    println!("\n--- JSON resultante ---");
    println!("{}", json_salida);
}

Conclusión

Como ves las posiblidades que ofrece rust-script son enormes. Puedes aprovechar la potencia y seguridad de Rust para escribir scripts robustos y mantenibles, sin la sobrecarga de configurar un proyecto completo con Cargo. Esto hace que rust-script sea una herramienta ideal para automatizaciones rápidas y tareas de scripting en Linux.

Me queda un segundo episodio para contarte algunas cosillas mas sobre rust-script, como por ejemplo, el uso de módulos, pruebas unitarias, y otras características avanzadas que pueden hacer que tus scripts sean aún más poderosos y flexibles. Así que no te lo pierdas. Pero sobre todo, convertir algunos de mis scripts de Bash en Rust con rust-script.


Más información,

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *