Как выполнить системную команду и захватить её вывод в Rust?
Правильный способ захвата вывода команды с преобразованием в строку
Вы всё делаете почти правильно, но забыли один важный шаг: output.stdout — это Vec<u8> (байтовый вектор), а не строка. Чтобы получить текст, нужно преобразовать байты в строку с помощью String::from_utf8_lossy() (или from_utf8() для строгой проверки).
Вот исправленный пример с полной обработкой:
use std::process::Command;
fn main() {
let result = Command::new("ls")
.arg("-la")
.output()
.expect("Не удалось запустить команду");
// Проверяем статус выхода
if result.status.success() {
// Преобразуем stdout в строку (lossy — заменяет невалидный UTF-8 на �)
let stdout = String::from_utf8_lossy(&result.stdout);
println!("Вывод команды:\n{}", stdout);
} else {
// Если ошибка — выводим stderr
let stderr = String::from_utf8_lossy(&result.stderr);
eprintln!("Команда завершилась с ошибкой: {}", result.status);
eprintln!("stderr:\n{}", stderr);
}
}
Что здесь происходит?
- Command::new("ls") — создаёт команду.
- .arg("-la") — добавляет аргумент.
- .output() — запускает команду и ждёт её завершения, возвращая
Output(содержит stdout, stderr и status). - result.status.success() — проверяет, завершилась ли команда с кодом 0.
- String::from_utf8_lossy() — безопасно преобразует байты в строку, заменяя невалидные последовательности на символ замены.
Более продвинутый пример с обработкой ошибок через Result
Если хотите избежать паники при ошибке запуска команды:
use std::process::Command;
fn run_command(cmd: &str, args: &[&str]) -> Result<String, String> {
let output = Command::new(cmd)
.args(args)
.output()
.map_err(|e| format!("Ошибка запуска '{}': {}", cmd, e))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("Команда '{}' завершилась с кодом {}: {}",
cmd, output.status, stderr))
}
}
fn main() {
match run_command("git", &["status"]) {
Ok(output) => println!("Git status:\n{}", output),
Err(e) => eprintln!("Ошибка: {}", e),
}
}
Важные замечания
- Не используйте
unwrap()в production-коде — лучшеexpect()с осмысленным сообщением или обработка черезmatch. - Для длительного вывода (например, логи) используйте
.stdout(Stdio::piped())и читайте построчно черезBufReader. - Кодировка:
from_utf8_lossyподходит для большинства систем (Linux/macOS обычно используют UTF-8). Для Windows может потребоваться перекодировка из UTF-16. - Безопасность: избегайте передачи пользовательского ввода напрямую в
Command::new()— используйте.arg()для каждого аргумента, чтобы избежать shell-инъекций.
Альтернатива: захват вывода с помощью оболочки (shell)
Иногда удобнее выполнить команду через оболочку, особенно если нужны пайпы (конвейеры) или подстановки. Для этого в Rust можно использовать sh -c:
use std::process::Command;
fn main() {
let output = Command::new("sh")
.arg("-c")
.arg("ls -la | grep '^d' | wc -l") // считаем количество директорий
.output()
.expect("Ошибка выполнения");
let stdout = String::from_utf8_lossy(&output.stdout);
println!("Количество директорий: {}", stdout.trim());
}
Важные предостережения
- Не используйте этот подход с пользовательским вводом — это риск shell-инъекций. Если нужно передать аргументы от пользователя, всегда используйте
.arg()без оболочки. - Кроссплатформенность: на Windows нужно использовать
cmd /Cвместоsh -c. Для кроссплатформенных проектов лучше избегать shell-команд или писать условную компиляцию. - Производительность: запуск оболочки добавляет накладные расходы. Для простых команд с одним процессом лучше использовать прямой вызов
Command::new().
Этот подход удобен для быстрых скриптов или прототипирования, но в серьёзных проектах старайтесь обходиться без оболочки.
Ответов пока нет.
Войдите чтобы оставить ответ