• 系统命令:调用grep

    系统命令:调用grep

    我们知道,Linux系统中有一个命令叫grep,他能对目标文件进行分析并查找相应字符串,并该字符串所在行输出。
    今天,我们先来写一个Rust程序,来调用一下这个 grep 命令

    1. use std::process::*;
    2. use std::env::args;
    3. // 实现调用grep命令搜索文件
    4. fn main() {
    5. let mut arg_iter = args();
    6. // panic if there is no one
    7. arg_iter.next().unwrap();
    8. let pattern = arg_iter.next().unwrap_or("main".to_string());
    9. let pt = arg_iter.next().unwrap_or("./".to_string());
    10. let output = Command::new("/usr/bin/grep")
    11. .arg("-n")
    12. .arg("-r")
    13. .arg(&pattern)
    14. .arg(&pt)
    15. .output()
    16. .unwrap_or_else(|e| panic!("wg panic because:{}", e));
    17. println!("output:");
    18. let st = String::from_utf8_lossy(&output.stdout);
    19. let lines = st.split("\n");
    20. for line in lines {
    21. println!("{}", line);
    22. }
    23. }

    看起来好像还不错,但是,以上的程序有一个比较致命的缺点——因为Output是同步的,因此,一旦调用的目录下有巨大的文件,grep的分析将占用巨量的时间。这对于一个高可用的程序来说是不被允许的。

    那么如何改进呢?

    其实在上面的代码中,我们隐藏了一个 Child 的概念,即——子进程。

    下面我来演示怎么操作子进程:

    1. use std::process::*;
    2. use std::env::args;
    3. // 实现调用grep命令搜索文件
    4. fn main() {
    5. let mut arg_iter = args();
    6. // panic if there is no one
    7. arg_iter.next();
    8. let pattern = arg_iter.next().unwrap_or("main".to_string());
    9. let pt = arg_iter.next().unwrap_or("./".to_string());
    10. let child = Command::new("grep")
    11. .arg("-n")
    12. .arg("-r")
    13. .arg(&pattern)
    14. .arg(&pt)
    15. .spawn().unwrap();
    16. // 做些其他的事情
    17. std::thread::sleep_ms(1000);
    18. println!("{}", "计算很费时间……");
    19. let out = child.wait_with_output().unwrap();
    20. let out_str = String::from_utf8_lossy(&out.stdout);
    21. for line in out_str.split("\n") {
    22. println!("{}", line);
    23. }
    24. }

    但是,这个例子和我们预期的并不太一样!

    1. ./demo main /home/wayslog/rust/demo/src
    2. /home/wayslog/rust/demo/src/main.rs:5:fn main() {
    3. /home/wayslog/rust/demo/src/main.rs:9: let pattern = arg_iter.next().unwrap_or("main".to_string());
    4. 计算很费时间……

    为什么呢?

    很简单,我们知道,在Linux中,fork出来的函数会继承父进程的所有句柄。因此,子进程也就会继承父进程的标准输出,也就是造成了这样的问题。这也是最后我们用out无法接收到最后的输出也就知道了,因为在前面已经被输出出来了呀!

    那么怎么做呢?给这个子进程一个pipeline就好了!

    1. use std::process::*;
    2. use std::env::args;
    3. // 实现调用grep命令搜索文件
    4. fn main() {
    5. let mut arg_iter = args();
    6. // panic if there is no one
    7. arg_iter.next();
    8. let pattern = arg_iter.next().unwrap_or("main".to_string());
    9. let pt = arg_iter.next().unwrap_or("./".to_string());
    10. let child = Command::new("grep")
    11. .arg("-n")
    12. .arg("-r")
    13. .arg(&pattern)
    14. .arg(&pt)
    15. // 设置pipeline
    16. .stdout(Stdio::piped())
    17. .spawn().unwrap();
    18. // 做些其他的事情
    19. std::thread::sleep_ms(1000);
    20. println!("{}", "计算很费时间……");
    21. let out = child.wait_with_output().unwrap();
    22. let out_str = String::from_utf8_lossy(&out.stdout);
    23. for line in out_str.split("\n") {
    24. println!("{}", line);
    25. }
    26. }

    这段代码相当于给了stdout一个缓冲区,这个缓冲区直到我们计算完成之后才被读取,因此就不会造成乱序输出的问题了。

    这边需要注意的一点是,一旦你开启了一个子进程,那么,无论你程序是怎么处理的,最后一定要记得对这个child调用wait或者wait_with_output,除非你显式地调用kill。因为如果父进程不wait它的话,它将会变成一个僵尸进程!!!

    : 以上问题为Linux下Python多进程的日常问题,已经见怪不怪了。