【问题标题】:How can I wait for a Rust `Child` process whose stdout has been piped to another?我如何等待其标准输出已通过管道传输到另一个的 Rust `Child` 进程?
【发布时间】:2019-05-15 15:34:04
【问题描述】:

我想在 Rust 中正确地实现 yes | head -n 1 连接管道并检查退出状态:即,我希望能够 确定yesSIGPIPE 而退出并且head 完成 一般。管道功能很简单 (Rust Playground):

use std::process;

fn a() -> std::io::Result<()> {
    let child_1 = process::Command::new("yes")
        .arg("abracadabra")
        .stdout(process::Stdio::piped())
        .spawn()?;
    let pipe: process::ChildStdout = child_1.stdout.unwrap();
    let child_2 = process::Command::new("head")
        .args(&["-n", "1"])
        .stdin(pipe)
        .stdout(process::Stdio::piped())
        .spawn()?;
    let output = child_2.wait_with_output()?;
    let result = String::from_utf8_lossy(&output.stdout);
    assert_eq!(result, "abracadabra\n");
    println!("Good from 'a'.");
    Ok(())
}

虽然我们可以随时等待child_2,但声明 pipe 移动 child_1,所以不清楚如何等待 child_1。 如果我们只是在assert_eq! 之前添加child_1.wait()?,我们会 遇到编译时错误:

error[E0382]: borrow of moved value: `child_1`
  --> src/main.rs:15:5
   |
8  |     let pipe: process::ChildStdout = child_1.stdout.unwrap();
   |                                      -------------- value moved here
...
15 |     child_1.wait()?;
   |     ^^^^^^^ value borrowed here after partial move
   |
   = note: move occurs because `child_1.stdout` has type `std::option::Option<std::process::ChildStdout>`, which does not implement the `Copy` trait

我们可以通过unsafe 和特定平台管理wait 功能(Rust Playground):

use std::process;

fn b() -> std::io::Result<()> {
    let mut child_1 = process::Command::new("yes")
        .arg("abracadabra")
        .stdout(process::Stdio::piped())
        .spawn()?;
    use std::os::unix::io::{AsRawFd, FromRawFd};
    let pipe: process::Stdio =
        unsafe { FromRawFd::from_raw_fd(child_1.stdout.as_ref().unwrap().as_raw_fd()) };
    let mut child_2 = process::Command::new("head")
        .args(&["-n", "1"])
        .stdin(pipe)
        .stdout(process::Stdio::piped())
        .spawn()?;
    println!("child_1 exited with: {:?}", child_1.wait().unwrap());
    println!("child_2 exited with: {:?}", child_2.wait().unwrap());
    let mut result_bytes: Vec<u8> = Vec::new();
    std::io::Read::read_to_end(child_2.stdout.as_mut().unwrap(), &mut result_bytes)?;
    let result = String::from_utf8_lossy(&result_bytes);
    assert_eq!(result, "abracadabra\n");
    println!("Good from 'b'.");
    Ok(())
}

打印出来:

child_1 exited with: ExitStatus(ExitStatus(13))
child_2 exited with: ExitStatus(ExitStatus(0))
Good from 'b'.

这对于这个问题来说已经足够了,但肯定有 必须是一种安全且便携的方式来执行此操作。

为了比较,这是我在 C 中处理任务的方式(没有 费心去捕捉child_2的输出):

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define FAILIF(e, msg) do { if (e) { perror(msg); return 1; } } while (0)

void describe_child(const char *name, int status) {
    if (WIFEXITED(status)) {
        fprintf(stderr, "%s exited %d\n", name, WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        fprintf(stderr, "%s signalled %d\n", name, WTERMSIG(status));
    } else {
        fprintf(stderr, "%s fate unknown\n", name);
    }
}

int main(int argc, char **argv) {
    int pipefd[2];
    FAILIF(pipe(pipefd), "pipe");

    pid_t pid_1 = fork();
    FAILIF(pid_1 < 0, "child_1: fork");
    if (!pid_1) {
        FAILIF(dup2(pipefd[1], 1) == -1, "child_1: dup2");
        FAILIF(close(pipefd[0]), "child_1: close pipefd");
        execlp("yes", "yes", "abracadabra", NULL);
        FAILIF(1, "child_1: execlp");
    }

    pid_t pid_2 = fork();
    FAILIF(pid_2 < 0, "child_2: fork");
    if (!pid_2) {
        FAILIF(dup2(pipefd[0], 0) == -1, "child_2: dup2");
        FAILIF(close(pipefd[1]), "child_2: close pipefd");
        execlp("head", "head", "-1", NULL);
        FAILIF(1, "child_2: execlp");
    }

    FAILIF(close(pipefd[0]), "close pipefd[0]");
    FAILIF(close(pipefd[1]), "close pipefd[1]");

    int status_1;
    int status_2;
    FAILIF(waitpid(pid_1, &status_1, 0) == -1, "waitpid(child_1)");
    FAILIF(waitpid(pid_2, &status_2, 0) == -1, "waitpid(child_2)");
    describe_child("child_1", status_1);
    describe_child("child_2", status_2);

    return 0;
}

保存到test.c 并使用make test &amp;&amp; ./test 运行:

abracadabra
child_1 signalled 13
child_2 exited 0

【问题讨论】:

  • make test — 你没有提供 Makefile。
  • @Shepmaster:确实;这是一个内置的隐含规则。试试吧! :-)
  • @Stargateur:Shepmaster 添加了那个错误,不是我,但我刚刚编辑了帖子以澄清。没必要生气。
  • 请注意,在询问实际错误时, 是个好主意。

标签: process rust waitpid


【解决方案1】:

使用Option::take:

let pipe = child_1.stdout.take().unwrap();

let child_2 = process::Command::new("head")
    .args(&["-n", "1"])
    .stdin(pipe)
    .stdout(process::Stdio::piped())
    .spawn()?;

let output = child_2.wait_with_output()?;
child_1.wait()?;

另见:

【讨论】:

猜你喜欢
  • 2018-12-03
  • 1970-01-01
  • 2013-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多