Search code examples
subprocessstdindeno

How to connect the stdin streams of parent and child processes?


I have problem to write stdin in deno version 1.37.0 (release, x86_64-pc-windows-msvc). I search in documentation, google, try the code and not work. I found related code in https://github.com/denoland/deno/issues/7713 but not compatible with the latest version.

I have code like this:

// parent.ts
const command = new Deno.Command(Deno.execPath(), {
  args: [
    "run",
    "-A",
    "child.ts"
  ],
  stdout: "piped",
  stdin: "piped"
});

const p = command.spawn();
// child.ts
const decoder = new TextDecoder();
for await (const chunk of Deno.stdin.readable) {
  const text = decoder.decode(chunk);
  console.log(text);
}

Deno.stdin.close();

Does anyone know how to solve it?


Solution

  • You can connect any standard I/O stream of the parent process to a child process by using the value "inherit" for the option corresponding to that stream (stdout, stderr, stdin) — for example:

    ./parent.ts:

    const childProcess = new Deno.Command(Deno.execPath(), {
      args: ["run", import.meta.resolve("./child.ts")],
      stderr: "inherit", // Connect stderr of child to stderr of this process
      stdin: "inherit", // Connect stdin of child to stdin of this process
      stdout: "inherit", // Connect stdout of child to stdout of this process
    }).spawn();
    
    await childProcess.status;
    
    

    In fact — when spawning a child process in Deno v1.37.0"inherit" is the default option value for these streams, so you don't need to specify it. (But I suggest doing so in case the default ever changes — defaults have changed in the past! See denoland/deno#17025 and denoland/deno#17334.)

    So that you can reproduce this example, here's the content of ./child.ts:

    // Raw mode needs to be enabled for this example — you can read more about it in Deno's API documentation:
    Deno.stdin.setRaw(true, { cbreak: true });
    
    for await (
      const str of Deno.stdin.readable.pipeThrough(new TextDecoderStream())
    ) {
      const timestamp = new Date().toISOString();
      console.log(timestamp, str);
    }
    
    

    Now, you can run the parent process in your temrinal using this command:

    deno run --allow-read --allow-run=deno parent.ts
    

    As you type into (the stdin of) your terminal, the data stream will be forwarded to the child process. The example child module uses console.log (which writes values to stdout, followed by a newline). Because the code above also specified to connect the stdout streams of the child and parent processes, you should see each of your inputs preceded by a timestamp on a new line in your terminal.

    If — for example — you type H e l l o W o r l d into your terminal, then you'll see output very similar to this:

    2023-09-26T01:03:35.640Z H
    2023-09-26T01:03:35.852Z e
    2023-09-26T01:03:35.892Z l
    2023-09-26T01:03:36.023Z l
    2023-09-26T01:03:36.273Z o
    2023-09-26T01:03:36.700Z  
    2023-09-26T01:03:36.936Z W
    2023-09-26T01:03:37.097Z o
    2023-09-26T01:03:37.152Z r
    2023-09-26T01:03:37.276Z l
    2023-09-26T01:03:37.365Z d
    
    

    To stop the Deno parent process, use Ctrl + c to send the interrupt signal (SIGINT).


    In Deno, the standard I/O strems (stdout, stderr, and stdin) each have a corresponding readable or writable property which is an instance of a web standard stream.

    There is a Stack Overflow tag for the topic of those streams:


    To do something such as connecting the parent stdin to the child and also write directly to the child stdin in your program code, you'll need to use additional third-party code to merge those input streams, such as this module in Deno's std library: https://deno.land/std@0.202.0/streams/merge_readable_streams.ts