Write to command's STDIN

Hi there!

I'm currently stuck at doing a pretty simple thing: I want to spawn a command and put a string into its stdin pipe. I've found some solutions like this one from StackOverflow (subprocess - Write to child process' stdin in Rust? - Stack Overflow):

let mut child = Command::new("cat")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .spawn()?;

let child_stdin = child.stdin.as_mut().unwrap();
child_stdin.write_all(b"Hello, world!\n")?;

But this doesn't work with commands that expect to have an STDIN as soon as they start. If a command reads from STDIN, and if there is nothing immediately exits, this doesn't work, as we are writing to stdin a few moments after spawning the command.

I've tried creating a file and attaching it using the .stdin() method, it works but is costly and has lots of other drawbacks (like needing to have write permissions, requiring to write to the disk, etc.)

I'd like to find a way to do this properly: spawn a command with an already-attached STDIN created from a string.

How can I do that?

Thanks in advance for your help :slight_smile:

On nightly there is std::pipe::pipe which should enable what you want if you pass the reader to stdin. I did not try if you can use the writer before spawning though.

3 Likes

That seems to work perfectly, thanks a lot :slight_smile:

Arguably that command is broken. It's not a normal requirement to have data immediately available to read; most programs will, and should, just block (or do something equivalent to blocking) until the input is available.

4 Likes

Maybe, but I need 100% compatibility with it (I'm not running commands I've made myself).

That’s fair. I mention it because sometimes people end up spending lots of effort to implement behavior that is unnecessarily constrained by misunderstanding what the standard usage is.

2 Likes

What did you do exactly? It wasn't immediately obvious to me and this could be helpful to see a small code example for other people stumbling on similar issues :slight_smile:

1 Like

Basically I'm writting a full-blown shell.

It's mostly done (I'm using it as my daily driver) but there are some little quirks here and there, this was one of the last ones.

Sometimes a user will pipe a string value into a command, and I had a case where a command acts on whatever it's given as an arguments OR as stdin. So it first checks if an input is provided, if not it uses the provided arguments immediatly (without waiting). Hence the problem I had.

1 Like

Ditto this, same question for me. Specifically, how did you hook up the pipe to the command?

It seems like this might be a good start at addressing the comment on improving the unstable module docs

Oh the implementation code? Here is an extract from the project:

// Actually run the command
let child = Command::new(cmd_path)
    .stdin({
        // Unfortunately, it's not possible to provide a direct string as an input to a command
        // We actually need to provide an actual file descriptor (as is a usual stdin "pipe")
        // So we create a new pair of pipes here...
        let (reader, mut writer) = std::pipe::pipe().unwrap();

        // ...write the string to one end...
        writer.write_all(string.as_bytes()).unwrap();

        // ...and then transform the other to pipe it into the command as soon as it spawns!
        Stdio::from(reader)
    })
    .spawn()?;

I'll probably see if I can add some examples to the docs, but I'm not sure this is the original use of this function (is it?). Plus, examples help but I can't tell about how exactly it's implemented, seems to open a file descriptor at the OS level, which is pretty low-level stuff.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.