Как выполнить системную команду и получить её вывод в Rust?
Основная проблема: байты 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.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги