捕获 stdout 和 stderr 到管道

2024-01-20

我想从子进程中读取 stderr 和 stdout,但它不起作用。

main.rs

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

fn main() {
    let mut child = Command::new("./1.sh")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap();

    let out = BufReader::new(child.stdout.take().unwrap());
    let err = BufReader::new(child.stderr.take().unwrap());

    out.lines().for_each(|line|
        println!("out: {}", line.unwrap())
    );
    err.lines().for_each(|line|
        println!("err: {}", line.unwrap())
    );

    let status = child.wait().unwrap();
    println!("{}", status);
}

1.sh

#!/bin/bash
counter=100
while [ $counter -gt 0 ]
do
   sleep 0.1
   echo "on stdout"
   echo "on stderr" >&2
   counter=$(( $counter - 1 ))
done
exit 0

此代码仅读取标准输出:

out: on stdout

如果我删除此代码中与 stdout 相关的所有内容并仅保留 stderr ,它将仅读取 stderr:

let mut child = Command::new("./1.sh")
    .stdout(Stdio::null())
    .stderr(Stdio::piped())
    .spawn()
    .unwrap();

let err = BufReader::new(child.stderr.take().unwrap());

err.lines().for_each(|line|
    println!("err: {}", line.unwrap())
);

Produces

err: on stderr

看起来它可以一次读取 stdout 或 stderr,但不能同时读取两者。我究竟做错了什么?

我正在使用 Rust 1.26.0-nightly (322d7f7b9 2018-02-25)


当我在 Linux 下的计算机上运行这个程序时,它大约每 0.1 秒从 stdout 打印一行,直到读取完所有 100 行,然后立即打印来自 stderr 的 100 行,然后程序打印调用程序的退出代码并终止。

当您从管道读取数据时,如果没有传入数据,默认情况下,您的程序将block直到有一些数据可用。当另一个程序终止或决定关闭其管道末端时,如果您在读取其他程序发送的所有内容后从管道中读取数据,则读取将返回零字节长度,表示“文件结束” “(即它与常规文件的机制相同)。

当程序写入管道时,操作系统会将数据存储在缓冲区中,直到管道的另一端读取它。该缓冲区的大小有限,因此如果它已满,write会阻塞。例如,可能发生的情况是,一端在从 stdout 读取时阻塞,而另一端在写入 stderr 时阻塞。您发布的 shell 脚本没有输出足够的数据来阻止,但如果我将计数器更改为从 10000 开始,它会在我的系统上从 5632 处阻塞,因为 stderr 已满,因为 Rust 程序尚未开始读取它。

我知道有两种解决方案可以解决这个问题:

  1. 将管道设置为非阻塞模式。非阻塞模式意味着如果读取或写入会阻塞,它会立即返回,并带有一个明显的错误代码来指示此情况。出现这种情况时,您可以切换到下一个管道并尝试该管道。为了避免在两个管道还没有数据时消耗所有 CPU,您通常需要使用类似的函数poll https://linux.die.net/man/2/poll等待任一管道有数据。

    Rust 标准库没有公开这些管道的非阻塞模式,但它提供了方便的wait_with_output https://doc.rust-lang.org/stable/std/process/struct.Child.html#method.wait_with_output方法正是我刚才描述的!然而,顾名思义,它仅在程序结束时返回。另外,stdout 和 stderr 被读入Vecs,所以如果输出很大,你的程序会消耗大量内存;您无法以流方式处理数据。

    use std::io::{BufRead, BufReader};
    use std::process::{Command, Stdio};
    
    fn main() {
        let child = Command::new("./1.sh")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .unwrap();
    
        let output = child.wait_with_output().unwrap();
    
        let out = BufReader::new(&*output.stdout);
        let err = BufReader::new(&*output.stderr);
    
        out.lines().for_each(|line|
            println!("out: {}", line.unwrap());
        );
        err.lines().for_each(|line|
            println!("err: {}", line.unwrap());
        );
    
        println!("{}", output.status);
    }
    

    如果你想手动使用非阻塞模式,你可以在类 Unix 系统上恢复文件描述符:AsRawFd https://doc.rust-lang.org/stable/std/process/struct.ChildStdout.html#impl-AsRawFd或 Windows 上的文件句柄AsRawHandle https://doc.rust-lang.org/stable/std/process/struct.ChildStdout.html#impl-AsRawHandle,然后您可以将它们传递给适当的操作系统 API。

  2. 在单独的线程上读取每个管道。我们可以在主线程上继续读取其中一个管道,并为另一个管道生成一个线程。

    use std::io::{BufRead, BufReader};
    use std::process::{Command, Stdio};
    use std::thread;
    
    fn main() {
        let mut child = Command::new("./1.sh")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .unwrap();
    
        let out = BufReader::new(child.stdout.take().unwrap());
        let err = BufReader::new(child.stderr.take().unwrap());
    
        let thread = thread::spawn(move || {
            err.lines().for_each(|line|
                println!("err: {}", line.unwrap());
            );
        });
    
        out.lines().for_each(|line|
            println!("out: {}", line.unwrap());
        );
    
        thread.join().unwrap();
    
        let status = child.wait().unwrap();
        println!("{}", status);
    }
    
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

捕获 stdout 和 stderr 到管道 的相关文章

随机推荐

  • .Net 中的反应式 Rx zip 队列

    我对反应式编程的概念相当陌生 我在用Bonsai http bonsai rx org 它通过 C 公开了一些但不是全部 Net rx 命令 我试图获得像这个弹珠图这样的行为 input1 1 2 3 4 5 6 7 input2 abc
  • 指向字符串的指针数组

    在 C 编程中 我们无法使用 scanf 函数获取指针数组的值 但是 int main char names 6 int loop scanf s names 1 printftf n s names 1 它正在工作 当我将输入作为程序提供
  • Sublime Text 2 的 Emacs“Goto Anything”(或即时搜索)?

    我尝试过崇高文本2 http www sublimetext com 2最近 我发现转到任何内容 https www youtube com watch v 36NIIgcrYzE对于浏览源代码非常有用 Ctrl P 文件 符号 https
  • Java 问题:MAC 操作系统中的内存和 CPU 使用情况

    我正在开发适用于 MAC 和 Windows 的 javaFx 应用程序 我发现与 Windows 相比 该应用程序在 MAC 中使用了极大的内存和 cpu 使用率 当我在 Windows 任务管理器中查看应用程序的活动时 它显示平均 80
  • Android 连接时在后台发送数据

    基本上 我需要我的应用程序在建立与网络的连接时向服务器发送一些数据 并且它必须在后台运行 就是这样 有没有办法让我的服务在连接时自动启动 或者我应该使用常规服务来测试连接并以 30 分钟的间隔运行该服务 我应该使用 AlarmManager
  • 无法将客户端 VPN 终端节点连接到 VPC 中的 RDS

    我使用一个安全组设置了一个客户端 VPN 端点 客户端 CIDR 10 0 132 0 22 与两个私有子网 10 0 2 0 24 和 10 0 3 0 24 关联 我还有一个使用相同的两个子网和相同的安全组的 RDS 数据库 安全组有一
  • 如何使用 aws-cli 删除 s3 中 1 个月及之前的文件?

    我的存储桶已经有很多文件 我想删除 1 个月或更早的文件 我想删除文件而不设置对象过期 有没有办法使用 aws cli 来做到这一点 谢谢 Found 一篇博文 http shout setfive com 2011 12 05 delet
  • 有没有一种编程方式可以知道 Node.js 应用程序正在 Heroku 中运行?

    我可以调用一些变量或函数来了解 Node js 应用程序是否正在 Heroku 中运行吗 就像是 if process heroku console log I m in Heroku 您使用通常的环境变量 只需在 Heroku 实例上设置
  • Openlayers获取鼠标下图块的图片url

    我正在寻找鼠标下图块的图像 url 使用最新版本v4 6 4 有任何想法吗 谢谢 图块源类包含有关图块网格的所有信息 tileSource getTileGrid 您可以访问它的加载函数 http openlayers org en lat
  • 控制器 @Mixin 在重新编译正在运行的应用程序后才起作用

    在我最新的 grails 2 3 0 项目中 我使用的是 Mixin混合辅助类的注释以保持我的controller更干 如果在控制器内进行了一些更改以强制重新编译控制器 则 mixin 才可以工作 初始编译后 grails run app
  • C中的printf如何对浮点数进行舍入?

    我正在尝试实施printf我想知道如何printf对浮点数进行舍入 因为我找不到一般规则 例如 如果输入 gt printf f 1f 2f 5f 12f 0 000099 0 000099 0 000099 0 000099 0 0000
  • Spring Integration jdbc:inbound-channel-adapter - 将 max-rows-per-poll 动态设置为节流

    我有一个 JDBC inbound channel adapter 设置 max rows per poll 动态以限制在通道上传递的消息 我有一个容量为 200 的 QueueChannel 入站通道适配器会将消息发送到此 QueueCh
  • 以编程方式切换复选框

    我有一个需要检查 不可检查的项目的 ListView 我已经设置了一个 ArrayAdapter 当前使用 android R layout simple list item multiple choice 作为行 并且所有内容都显示得很好
  • 如何使用 Javascript 获取网站上所有可用图片 URL 的列表?

    我很好奇 DownThemAll 是如何做到这一点的 他们使用 JavaScript 吗 如何使用 Javascript 获取网站中所有 url 的列表 使用集合 Links document links href Images docum
  • 在箱线图中添加每组的观察数

    继这个问题之后 如何在 ggplot2 箱线图中添加每组的观察数量并使用组平均值 https stackoverflow com questions 15660829 我想添加每组的观察数量ggplot箱线图也是如此 但我添加了一种颜色ae
  • Emacs Org 模式:执行简单的 python 代码

    如何在 Emacs 的 Org 模式下执行非常简单的 Python 代码 第一个示例工作正常 但是我无法让它给出最简单计算的结果 works begin src python def foo x if x gt 0 return x 10
  • 原型范围不起作用

    我在应用程序中创建了一个原型作用域 bean 并使用 setter 将其注入到另一个 bean 中 但是当我在类中使用注入的 bean 时 它总是使用相同的实例而不是每次都使用新实例 这是代码的快照
  • 如何更改选项卡栏项目的默认灰色?

    我尝试更改默认的灰色Tab Bar项目 但 Xcode 发现错误 我使用了一些代码 该代码是 import UIKit extension UIImage func makeImageWithColorAndSize color UICol
  • paypal 10544 网关拒绝错误的原因

    您好 请告诉我 paypal DoDirectPayment 10544 Gateway Decline 错误的可能原因 我查了很多资料都找不到原因 首先是强制性的 愚蠢的人类把戏 问题 您确定您使用的卡是有效的信用卡吗 如果您在现场 而不
  • 捕获 stdout 和 stderr 到管道

    我想从子进程中读取 stderr 和 stdout 但它不起作用 main rs use std process Command Stdio use std io BufRead BufReader fn main let mut chil