El control de procesos en Rust se divide en dos dominios: la gestión del ciclo de vida del proceso actual y la orquestación de procesos externos (subprocesos). Para finalizar el proceso actual, es vital distinguir entre un cierre limpio y uno abrupto. Retornar desde main es la forma más segura, ya que permite que se ejecuten los destructores (drop) de todas las variables en el stack. Sin embargo, std::process::exit(code) fuerza la terminación inmediata con un código específico, saltándose la fase de limpieza de memoria y destructores, lo que puede causar que recursos como archivos o bloqueos de mutex queden en un estado inconsistente. Por otro lado, std::process::abort() detiene el proceso de forma inmediata mediante una señal de aborto del sistema, sin realizar ningún tipo de limpieza.

Para la ejecución de comandos externos, Rust proporciona el módulo std::process::Command, que implementa el patrón builder. Este módulo permite configurar el entorno, el directorio de trabajo y la comunicación mediante tuberías (pipes). Existen tres métodos principales para ejecutar un comando: spawn() para lanzar un proceso en segundo plano (retornando un objeto Child), status() para esperar la finalización y obtener solo el código de salida, y output() para esperar la ejecución y capturar toda la salida estándar (stdout) y de error (stderr) en una estructura Output. Para enviar datos al stdin de un subproceso, se debe configurar stdin(Stdio::piped()) y utilizar el método take() sobre el handle de entrada del proceso hijo para obtener un escritor (Write).

use std::process::{Command, Stdio};
use std::io::Write;
use std::env;

fn main() {
    // 1. Interacción con el entorno del proceso actual
    let args: Vec<String> = env::args().collect();
    println!("Argumentos recibidos: {:?}", args);

    let home = env::var("HOME").unwrap_or_else(|_| "No definida".to_string());
    println!("Variable HOME: {}", home);

    // 2. Ejecución de un subproceso con captura de salida (output)
    // Usamos 'echo' para demostrar la captura de stdout
    let output = Command::new("echo")
        .arg("Hola desde un subproceso")
        .output()
        .expect("Error al ejecutar el comando echo");

    if output.status.success() {
        let stdout_string = String::from_utf8_lossy(&output.stdout);
        println!("Salida capturada: {}", stdout_string.trim());
    }

    // 3. Ejecución de un subproceso con comunicación via stdin (piped)
    // Configuramos stdin como una tubería para poder escribir en él
    let mut child = Command::new("cat")
        .stdin(Stdio::piped())
        .spawn()
        .expect("Error al spawnear el proceso cat");

    // Obtenemos el handle de escritura del stdin del hijo
    // .take() mueve la propiedad del stdin al scope local
    let mut stdin_handle = child.stdin.take().expect("Error al tomar stdin");

    // Escribimos datos directamente en el proceso hijo
    stdin_handle.write_all(b"Texto enviado vía stdin").expect("Error al escribir en stdin");
    
    // El handle se cierra al salir del scope o al ser dropado, enviando EOF al hijo
    drop(stdin_handle);

    // Esperamos a que el proceso hijo termine para obtener su estado
    let status = child.wait().expect("Error al esperar al proceso");
    println!("El proceso 'cat' terminó con estado: {}", status);
}

Explicación del Código

  • env::args().collect(): Recupera los argumentos pasados por línea de comandos al proceso actual y los almacena en un Vec<String>.
  • env::var("HOME"): Intenta obtener el valor de la variable de entorno HOME. Se usa unwrap_or_else para manejar el caso de error si la variable no existe.
  • Command::new("echo"): Inicializa un constructor para el comando echo.
  • .arg("Hola desde un subproceso"): Añade un argumento al comando antes de ejecutarlo.
  • .output(): Ejecuta el comando, espera a que termine y captura su salida en una estructura Output. La variable output contiene stdout, stderr y status.
  • .status.success(): Verifica si el proceso terminó con un código de salida exitoso (típicamente 0).
  • String::from_utf8_lossy(&output.stdout): Convierte los bytes capturados en stdout a una cadena legible, manejando caracteres no UTF-8 de forma segura.
  • Command::new("cat").stdin(Stdio::piped()): Configura el comando cat para que su entrada estándar sea una tubería controlada por el proceso padre.
  • .spawn(): Ejecuta el proceso en segundo plano de forma asíncrona, permitiéndonos interactuar con él mientras corre, devolviendo un Child.
  • child.stdin.take(): El método take() consume el Option<ChildStdin> que reside en el objeto child, moviendo la propiedad del stream de entrada al stdin_handle. Esto es necesario para poder escribir en él de forma exclusiva.
  • stdin_handle.write_all(b"..."): Escribe una secuencia de bytes directamente en el canal de entrada del proceso hijo.
  • child.wait(): Bloquea la ejecución del proceso padre hasta que el proceso hijo termine, devolviendo su ExitStatus.

123