编辑

Creating a subprocess {#creating-a-subprocess}

Concepts {#concepts}

  • Deno is capable of spawning a subprocess via Deno.run.
  • --allow-run permission is required to spawn a subprocess.
  • Spawned subprocesses do not run in a security sandbox.
  • Communicate with the subprocess via the stdin, stdout and stderr streams.
  • Use a specific shell by providing its path/name and its string input switch, e.g. Deno.run({cmd: ["bash", "-c", "ls -la"]});

Simple example {#simple-example}

This example is the equivalent of running 'echo hello' from the command line.

/**
 * subprocess_simple.ts
 */

// define command used to create the subprocess
const cmd = ["echo", "hello"];

// create subprocess
const p = Deno.run({ cmd });

// await its completion
await p.status();

Note: If using Windows, the command above would need to be written differently because echo is not an executable binary (rather, it is a built-in shell command):

// define command used to create the subprocess
const cmd = ["cmd", "/c", "echo hello"];

Run it:

$ deno run --allow-run ./subprocess_simple.ts
hello

Security {#security}

The --allow-run permission is required for creation of a subprocess. Be aware that subprocesses are not run in a Deno sandbox and therefore have the same permissions as if you were to run the command from the command line yourself.

Communicating with subprocesses {#communicating-with-subprocesses}

By default when you use Deno.run() the subprocess inherits stdin, stdout and stderr of the parent process. If you want to communicate with started subprocess you can use "piped" option.

/**
 * subprocess.ts
 */
const fileNames = Deno.args;

const p = Deno.run({
  cmd: [
    "deno",
    "run",
    "--allow-read",
    "https://deno.land/std@0.155.0/examples/cat.ts",
    ...fileNames,
  ],
  stdout: "piped",
  stderr: "piped",
});

const { code } = await p.status();

// Reading the outputs closes their pipes
const rawOutput = await p.output();
const rawError = await p.stderrOutput();

if (code === 0) {
  await Deno.stdout.write(rawOutput);
} else {
  const errorString = new TextDecoder().decode(rawError);
  console.log(errorString);
}

Deno.exit(code);

When you run it:

$ deno run --allow-run ./subprocess.ts <somefile>
[file content]

$ deno run --allow-run ./subprocess.ts non_existent_file.md

Uncaught NotFound: No such file or directory (os error 2)
    at DenoError (deno/js/errors.ts:22:5)
    at maybeError (deno/js/errors.ts:41:12)
    at handleAsyncMsgFromRust (deno/js/dispatch.ts:27:17)

Piping to files

This example is the equivalent of running yes &> ./process_output in bash.

/**
 * subprocess_piping_to_file.ts
 */

import {
  readableStreamFromReader,
  writableStreamFromWriter,
} from "https://deno.land/std@0.155.0/streams/conversion.ts";
import { mergeReadableStreams } from "https://deno.land/std@0.155.0/streams/merge.ts";

// create the file to attach the process to
const file = await Deno.open("./process_output.txt", {
  read: true,
  write: true,
  create: true,
});
const fileWriter = await writableStreamFromWriter(file);

// start the process
const process = Deno.run({
  cmd: ["yes"],
  stdout: "piped",
  stderr: "piped",
});

// example of combining stdout and stderr while sending to a file
const stdout = readableStreamFromReader(process.stdout);
const stderr = readableStreamFromReader(process.stderr);
const joined = mergeReadableStreams(stdout, stderr);
// returns a promise that resolves when the process is killed/closed
joined.pipeTo(fileWriter).then(() => console.log("pipe join done"));

// manually stop process "yes" will never end on its own
setTimeout(async () => {
  process.kill("SIGINT");
}, 100);

Run it:

$ deno run --allow-run ./subprocess_piping_to_file.ts