热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过管道捕获标准输出和标准错误

如何解决《通过管道捕获标准输出和标准错误》经验,为你挑选了1个好方法。

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

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

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);
}

此代码仅读取stdout:

#!/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

如果我在这段代码中删除了所有与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())
);

产生

out: on stdout

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

我每晚使用Rust 1.26.0(322d7f7b9 2018-02-25)



1> Francis Gagn..:

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

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

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

我知道解决此问题的两种解决方案:

    将管道设置为非阻塞模式。非阻塞模式意味着如果读取或写入将被阻塞,它将立即返回一个带有指示此情况的独特错误代码的返回。发生这种情况时,您可以切换到下一个管道并尝试使用该管道。为了避免在两个管道都没有数据时消耗所有CPU,通常需要使用一个函数,例如poll等到两个管道都没有数据时再使用。

    Rust标准库没有公开这些管道的非阻塞模式,但是它提供了一种方便的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的文件描述符,或者使用来恢复Windows上的文件句柄AsRawHandle,然后可以将它们传递给适当的操作系统API。

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

    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);
    }
    


推荐阅读
author-avatar
手机用户2502940097
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有