В нашей последовательной серии материалов мы рассмотрим базовые основы новомодного языка Rust. А во второй части цикла на основе изученного попробуем написать самые простые смарт-контракты для таких блокчейн-проектов, как Solana. В этом туториале будет много примеров, мало теории и быстрый темп продвижения.
Этот пост — вольный перевод на русский вот этой оригинальной статьи (с нашими дополнениями в местах, где это показалось нужным), которую написал Стив Донован. Начало можно найти вот здесь, а оглавление всей серии — вот тут.
Эта часть посвящена модулю std::process, который занимается запуском и обработкой процессов (преимущественно дочерних).
Фундаментальная необходимость заключается в том, чтобы ваши программы на Rust корректно запускали другие программы (или, точнее, запускали процессы). Ваша программа может породить столько дочерних процессов, сколько захочет, и, как следует из названия, они имеют особые отношения со своим родителем.
Запустить программу очень просто с помощью структуры Command
, которая формирует аргументы для передачи программе:
use std::process::Command;
fn main() {
let status = Command::new("rustc")
.arg("-V")
.status()
.expect("no rustc?");
println!("cool {} code {}", status.success(), status.code().unwrap());
}
// rustc 1.15.0-nightly (8f02c429a 2016-12-15)
// cool true code 0
Итак, что здесь происходит: new
получает имя программы (оно будет искаться в PATH
, если это не абсолютное имя файла), arg
добавляет новый аргумент, а status
вызывает ее запуск. Все это вместе возвращает Result
, который является Ok
, а в случае если программа действительно была запущена, также содержит ExitStatus
.
В данном случае программа завершилась успешно и вернула код выхода 0.
Если мы заменим -V
на -v
(легкая ошибка), то rustc
завершится неудачей:
error: no input filename
given cool false code 101
Итак, всего есть три варианта:
По умолчанию потоки стандартного вывода и стандартной ошибки программы идут в терминал.
Часто мы очень заинтересованы в захвате этого вывода, поэтому существует соответствующий метод вывода.
/ process2.rs
use std::process::Command;
fn main() {
let output = Command::new("rustc")
.arg("-V")
.output()
.expect("no rustc?");
if output.status.success() {
println!("ok!");
}
println!("len stdout {} stderr {}", output.stdout.len(), output.stderr.len());
}
// ok!
// len stdout 44 stderr 0
Как и в случае со статусом, наша программа блокируется до завершения дочернего процесса, и мы получаем обратно три вещи — статус (как и раньше), содержимое stdout
и содержимое stderr
.
Захваченный вывод — это просто Vec<u8>
— просто байты. Напомним, что у нас нет гарантии, что данные, которые мы получаем от операционной системы, являются правильно закодированной строкой UTF-8. Фактически у нас нет гарантии, что это вообще строка — программы могут возвращать произвольные двоичные данные.
Если мы уверены, что выходные данные являются UTF-8, то String::from_utf8
преобразует эти векторы или байты — что возвращает Result
, потому что это преобразование может оказаться неудачным. Более небрежной функцией является String::from_utf8_lossy
, которая сделает хорошую попытку преобразования и вставит недопустимый знак Unicode � там, где это не удалось.
Вот еще одна полезная функция, которая запускает программу с помощью оболочки. Она использует обычный механизм оболочки для соединения stderr
с stdout
. В Windows имя оболочки отличается, но в остальном все работает, как и ожидалось.
fn shell(cmd: &str) -> (String,bool) {
let cmd = format!("{} 2>&1",cmd);
let shell = if cfg!(windows) {"cmd.exe"} else {"/bin/sh"};
let flag = if cfg!(windows) {"/c"} else {"-c"};
let output = Command::new(shell)
.arg(flag)
.arg(&cmd)
.output()
.expect("no shell?");
(
String::from_utf8_lossy(&output.stdout).trim_right().to_string(),
output.status.success()
)
}
fn shell_success(cmd: &str) -> Option<String> {
let (output,success) = shell(cmd);
if success {Some(output)} else {None}
}
Мы обрезаем все пробелы справа, чтобы, если вы скажете shell("which rustc")
, то получили бы путь без лишних строк.
Вы можете контролировать выполнение программы, запущенной Process
, указывая каталог, в котором она будет выполняться, используя метод current_dir
и переменные окружения, которые она видит, используя env
.
До сих пор наша программа просто ждала завершения дочернего процесса. Если вы используете метод spawn
, то мы возвращаемся немедленно и должны явно дождаться завершения — или уйти и заняться чем-то другим в это время. Этот пример ниже также показывает, как подавить стандартный выход и стандартную ошибку:
// process5.rs
use std::process::{Command,Stdio};
fn main() {
let mut child = Command::new("rustc")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("no rustc?");
let res = child.wait();
println!("res {:?}", res);
}
По умолчанию дочерняя программа наследует стандартный ввод и вывод родительской программы. В данном случае мы перенаправляем направление дочернего вывода в «никуда». Это эквивалентно тому, чтобы сказать > /dev/null 2> /dev/null
в оболочке Unix.
В Rust можно делать подобные вещи, используя оболочку (sh
или cmd
). И таким образом вы получаете полный программный контроль над созданием процессов.
Например, если у нас просто .stdout(Stdio::piped())
, то стандартный вывод дочернего процесса перенаправляется в пайп. Тогда child.stdout
— это то, что вы можете использовать для прямого чтения вывода (то есть это реализует Read
). Аналогично вы можете использовать метод .stdout(Stdio::piped())
, чтобы писать в child.stdin
.
Но если мы использовали wait_with_output
вместо wait
, то он возвращает Result<Output>
, а вывод дочернего объекта, как и раньше, записывается в поле stdout
этого Output
как Vec<u8>
.
Структура Child
также предоставляет вам явный метод kill
.
Для порождения процесса можно использовать несколько методов Command
, таких как spawn
или output
. В частности, output
порождает дочерний процесс и ждет, пока он завершится, а spawn
возвращает Child
, представляющий сам порожденный дочерний процесс.
В завершение приведем развернутый пример обработки ввода-вывода (Process I/O).
Stdout
, stdin
и stderr
дочернего процесса могут быть настроены путем передачи Stdio
соответствующему методу в Command
. После порождения к ним можно получить доступ из дочернего процесса. Например, передача вывода из одной команды в другую может быть выполнена следующим образом:
use std::process::{Command, Stdio};
// stdout must be configured with `Stdio::piped` in order to use
// `echo_child.stdout`
let echo_child = Command::new("echo")
.arg("Oh no, a tpyo!")
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start echo process");
// Note that `echo_child` is moved here, but we won't be needing
// `echo_child` anymore
let echo_out = echo_child.stdout.expect("Failed to open echo stdout");
let mut sed_child = Command::new("sed")
.arg("s/tpyo/typo/")
.stdin(Stdio::from(echo_out))
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start sed process");
let output = sed_child.wait_with_output().expect("Failed to wait on sed");
assert_eq!(b"Oh no, a typo!\n", output.stdout.as_slice());
Обратите внимание, что ChildStderr
и ChildStdout
реализуют чтение, а ChildStdin
реализует запись:
use std::process::{Command, Stdio};
use std::io::Write;
let mut child = Command::new("/bin/cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
// If the child process fills its stdout buffer, it may end up
// waiting until the parent reads the stdout, and not be able to
// read stdin in the meantime, causing a deadlock.
// Writing from another thread ensures that stdout is being read
// at the same time, avoiding the problem.
let mut stdin = child.stdin.take().expect("failed to get stdin");
std::thread::spawn(move || {
stdin.write_all(b"test").expect("failed to write to stdin");
});
let output = child
.wait_with_output()
.expect("failed to wait on child");
assert_eq!(b"test", output.stdout.as_slice());
Итак, как видно из примера выше, модуль std::process
в основном занимается порождением и взаимодействием с дочерними процессами, но он также предоставляет abort
и exit
для завершения текущего процесса.
Продолжение следует…
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…