0

I have the following Rust code that sends a string to a subprocess via stdin and read whatever comes from it's stdout:

use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
fn main() {
   let mut child = Command::new("cat")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Did not run");
    let v = vec!["11111", "2222222", "3333", "end", "soooooooooooooooo"];
    let k = child.stdin.as_mut().unwrap();
    let mut g = BufReader::new(child.stdout.as_mut().unwrap());
    for x in v.into_iter() {
        k.write_all(x.as_bytes()).unwrap();
        k.write_all("\n".as_bytes()).unwrap();
        let mut s: String = String::new();
        g.read_line(&mut s).unwrap();
        println!("{}", s)
    }
}

The example runs without issues but when I try to do the same using a python script it fails.

To call the script I'm creating child like this:

    let mut child = Command::new("c:\\Windows\\py.exe")
        .args(&["-u", "echo.py"])

And in the script I'm just echoing whatever comes from stdin to stdout

import sys
import time
if __name__=="__main__":
    fo=sys.stdout
    fo.write('python starts at: %.0f\n'%(time.time()*1000))
    line = sys.stdin.readline()
    while True:
        fo.write(line+"\n")
        line = sys.stdin.readline()
    fo.write('python ends at: %.0f\n'%(time.time()*1000))

The output I'm getting is:

python starts at: 1582166542281                                                                

11111                                                                                          



2222222                                                                                        



Traceback (most recent call last):                                                             
  File "c:\Users\asia\Documents\projects\python\echo.py", line 8, in <module>                  
    fo.write(line+"\n")                                                                        
OSError: [Errno 22] Invalid argument                                                           

My take is the pipe breaks somewhere around printing the second string but I can't figure out why. What am I missing here?

Thanks in advance

yorodm
  • 4,359
  • 24
  • 32
  • 1
    I'd guess that this is due to Windows LF→CRLF line ending conversions. Can you try [this answer](https://stackoverflow.com/a/34997357/5397009) and see if it helps? – Jmb Feb 20 '20 at 08:11

1 Answers1

1

Rust process writes 5 lines on the stdin of python process, precisely the strings coming from:

let v = vec!["11111", "2222222", "3333", "end", "soooooooooooooooo"];

But on the python side you are trying to write back to rust 7 lines: an header, the 5 lines coming from stdin and a footer.

The read_line inside for x in v.into_iter() reads only 5 lines and the rust process terminates, triggering a BrokenPipe on python side that has to write two more lines.

I think it is also needed some control to exit the while True loop on python side.

Something along these lines, just to get the idea:

use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
fn main() {
   let mut child = Command::new("python").args(&["-u", "echo.py"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Did not run");
    let v = vec!["11111", "2222222", "3333", "end", "soooooooooooooooo"];
    let k = child.stdin.as_mut().unwrap();
    let mut g = BufReader::new(child.stdout.as_mut().unwrap());

    // read the header "python starts at ..."
    let mut s: String = String::new();
    g.read_line(&mut s).unwrap();
    println!("{}", s);

    for x in v.into_iter() {
        k.write_all(x.as_bytes()).unwrap();
        k.write_all("\n".as_bytes()).unwrap();
        let mut s: String = String::new();

        // read len(v) lines, in this case 5 lines
        g.read_line(&mut s).unwrap();
        println!("{}", s)
    }

    // Something is neeeded to signal termination of input
    // assume for this example a zero length string
    k.write_all("\n".as_bytes()).unwrap();

    // read the empty string and the footer "python ends at ..."
    let mut s: String = String::new();
    g.read_line(&mut s).unwrap();
    g.read_line(&mut s).unwrap();
    println!("{}", s);
}

python:

import sys
import time

if __name__ == "__main__":
    fo = sys.stdout
    fo.write('python starts at: %.0f\n' % (time.time()*1000))
    line = sys.stdin.readline()
    while True:
        fo.write(line)
        line = sys.stdin.readline()
        if (line == "\n"):
            break

    fo.write('python ends at: %.0f\n' % (time.time()*1000))
attdona
  • 17,196
  • 7
  • 49
  • 60
  • I facepalmed myself this morning after looking at the code again. Thanks for the answer tho. – yorodm Feb 20 '20 at 14:08