Avatar ·

Как выполнить системную команду и получить её вывод в Rust?

Всем привет! Я новичок в Rust и пытаюсь выполнить простую системную команду, например `ls -la`, и получить её вывод в виде строки. В Python я бы сделал `subprocess.check_output(...)`, а здесь ничего не работает. Пробую так: ```rust use std::process::Command; fn main() { let output = Command::new("ls") .arg("-la") .output() .expect("failed to execute process"); println!("{}", output.stdout); } ``` Но в консоль выводится что-то вроде `[115, 116, 114, ...]` — какие-то числа, а не человеческий текст. Я пробовал добавить `.to_string()`, но компилятор ругается. Ещё я слышал про `std::process::Output`, но не понимаю, как из него извлечь именно текст. Подскажите, как правильно выполнить команду и получить её вывод как строку? Желательно с обработкой ошибок — что делать, если команда не найдена или завершилась с ненулевым кодом. Заранее спасибо! <<>>

Основная проблема: байты vs строка

Вы почти всё сделали правильно. output.stdout — это Vec<u8> (вектор байтов), поэтому при печати вы видите числа. Чтобы получить строку, нужно явно преобразовать байты в String.

use std::process::Command;

fn main() {
    let output = Command::new("ls")
        .arg("-la")
        .output()
        .expect("не удалось выполнить команду");

    // Преобразуем Vec<u8> в String
    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    println!("stdout:\n{}", stdout);
    if !output.status.success() {
        eprintln!("Команда завершилась с ошибкой: {}", output.status);
        eprintln!("stderr:\n{}", stderr);
    }
}

Правильная обработка ошибок

Лучше не использовать expect, а обрабатывать все варианты через Result:

use std::process::{Command, Output};
use std::io;

fn run_command(cmd: &str, args: &[&str]) -> Result<String, io::Error> {
    let output: Output = Command::new(cmd)
        .args(args)
        .output()?;  // ошибка если команда не найдена или не запустилась

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(io::Error::new(
            io::ErrorKind::Other,
            format!("команда '{}' завершилась с кодом {}. stderr: {}", 
                    cmd, output.status, stderr)
        ))
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = run_command("ls", &["-la"])?;
    println!("{}", result);
    Ok(())
}

Альтернативы с использованием крейтов

Для сложных сценариев рекомендую крейт duct — он даёт более читаемый синтаксис:

// Cargo.toml: duct = "0.13"
use duct::cmd;

fn main() {
    let output = cmd!("ls", "-la")
        .stdout_capture()
        .stderr_capture()
        .read()
        .expect("ошибка выполнения");

    println!("{}", output);
}

Важные моменты

  • Кодировка: String::from_utf8_lossy заменяет невалидные UTF-8 последовательности на символ замены. Если нужна строгая проверка — используйте String::from_utf8.
  • Паника: expect паникует при ошибке. Для библиотечного кода всегда используйте Result.
  • Большие объёмы данных: Если вывод команды может быть огромным, используйте .stdout(Stdio::piped()) и читайте построчно, чтобы избежать переполнения памяти.
  • Shell-команды: Команды вроде echo $HOME не работают напрямую — они требуют shell. Используйте Command::new("sh").args(&["-c", "echo $HOME"]).
<<>>

Использование каналов и асинхронного чтения

Если вывод команды очень большой или вы хотите обрабатывать его по мере поступления, используйте stdout(Stdio::piped()) и читайте через BufReader:

use std::process::{Command, Stdio};
use std::io::{BufRead, BufReader};

fn main() {
    let mut child = Command::new("ping")
        .arg("-c")
        .arg("3")
        .arg("google.com")
        .stdout(Stdio::piped())
        .spawn()
        .expect("не удалось запустить");

    if let Some(stdout) = child.stdout.take() {
        let reader = BufReader::new(stdout);
        for line in reader.lines() {
            match line {
                Ok(text) => println!("{}", text),
                Err(e) => eprintln!("Ошибка чтения: {}", e),
            }
        }
    }

    let status = child.wait().expect("ошибка ожидания");
    println!("Команда завершилась с кодом: {}", status);
}

Этот подход позволяет не ждать завершения всей команды, а сразу выводить результаты. Особенно полезно для длительных процессов (например, ping, tail -f).

Примечание: Не забудьте обработать child.stdout.take() — если не забрать канал, процесс может зависнуть. В реальном коде используйте if let Some(..) или unwrap.