作者:手机用户2502940097 | 来源:互联网 | 2022-12-25 22:51
我想从子进程中读取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被读入Vec
s,因此,如果输出很大,则程序将消耗大量内存;您不能以流式处理数据。
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);
}