我如何 运行 来自 Deno 的任意 shell 命令?

How do I run an arbitrary shell command from Deno?

我想 运行 来自 Deno 的任意 bash 命令,就像我在 Node.js 中使用 child_process 一样。这在 Deno 中可能吗?

您可以像这样使用 run 来做到这一点:

// myscript.js
Deno.run({
  cmd: ["echo", "hello world"]
})

您必须 --allow-run 当 运行 脚本才能运行时:

deno run --allow-run ./myscript.js

为了运行一个shell命令,你必须使用Deno.run,这需要--allow-run权限。

有一个 ongoing discussion 可以使用 --allow-all 代替 运行 子进程


下面会输出到stdout.

// --allow-run
const process = Deno.run({
  cmd: ["echo", "hello world"]
});

// Close to release Deno's resources associated with the process.
// The process will continue to run after close(). To wait for it to
// finish `await process.status()` or `await process.output()`.
process.close();

如果要存储输出,则必须将 stdout/stderr 设置为 "piped"

const process = Deno.run({
  cmd: ["echo", "hello world"], 
  stdout: "piped",
  stderr: "piped"
});


const output = await process.output() // "piped" must be set
const outStr = new TextDecoder().decode(output);

/* 
const error = await p.stderrOutput();
const errorStr = new TextDecoder().decode(error); 
*/

process.close();

确保 await status or output of the child process created with Deno.run.

否则,进程可能会在执行任何代码之前被杀死。例如:

deno run --allow-run main.ts
main.ts:
const p = Deno.run({
  cmd: ["deno", "run", "--allow-write", "child.ts"],
});
const { code } = await p.status(); // (*1); wait here for child to finish
p.close();
child.ts:
// If we don't wait at (*1), no file is written after 3 sec delay
setTimeout(async () => {
  await Deno.writeTextFile("file.txt", "Some content here");
  console.log("finished!");
}, 3000);

通过 stdin / stdout:

传递参数 main.ts:
const p = Deno.run({
  cmd: ["deno", "run", "--allow-write", "child.ts"],
  // Enable pipe between processes
  stdin: "piped",
  stdout: "piped",
  stderr: "piped",
});
if (!p.stdin) throw Error();

// pass input to child
await p.stdin.write(new TextEncoder().encode("foo"));
await p.stdin.close();

const { code } = await p.status();
if (code === 0) {
  const rawOutput = await p.output();
  await Deno.stdout.write(rawOutput); // could do some processing with output
} else { /* error */ }
child.ts:
import { readLines } from "https://deno.land/std/io/bufio.ts"; // convenient wrapper

// read given input argument
let args = "";
for await (const line of readLines(Deno.stdin)) {
  args += line;
}

setTimeout(async () => {
  await Deno.writeTextFile("file.txt", `Some content here with ${args}`);
  console.log(`${args} finished!`); // prints "foo finished!""
}, 3000);

Deno docs 中也有很好的示例资源。

或者,您也可以通过任务运行程序调用 shell 命令,例如 drake,如下所示

import { desc, run, task, sh } from "https://deno.land/x/drake@v1.5.0/mod.ts";

desc("Minimal Drake task");
task("hello", [], async function () {
  console.log("Hello World!");
  await sh("deno run --allow-env src/main.ts");
});

run();

$ deno run -A drakefile.ts hello

如果您的 shell 命令在进程即将结束之前打印出一些消息,您确实希望通过管道将 stdin 和 stdout 传输到您自己的流,并抛出一个您可以捕获的异常。

您甚至可以在将流程流传输到您自己的流时更改输出:

async function run(cwd, ...cmd) {
    const stdout = []
    const stderr = []
    cwd = cwd || Deno.cwd()

    const p = Deno.run({
        cmd,
        cwd,
        stdout: "piped",
        stderr: "piped"
    })
    console.debug(`$ ${cmd.join(" ")}`)

    const decoder = new TextDecoder()
    
    streams.readableStreamFromReader(p.stdout).pipeTo(new WritableStream({
        write(chunk) {
            for (const line of decoder.decode(chunk).split(/\r?\n/)) {
                stdout.push(line)
                console.info(`[ ${cmd[0]} ] ${line}`)
            }
        },
    }))

    streams.readableStreamFromReader(p.stderr).pipeTo(new WritableStream({
        write(chunk) {
            for (const line of decoder.decode(chunk).split(/\r?\n/)) {
                stderr.push(line)
                console.error(`[ ${cmd[0]} ] ${line}`)
            }
        },
    }))

    const status = await p.status()
    if (!status.success) {
        throw new Error(`[ ${cmd[0]} ] failed with exit code ${status.code}`)
    }
    return {
        status,
        stdout,
        stderr,
    }
}

如果你对每个可写流没有不同的逻辑,你也可以将它们合并为一个:

    streams.mergeReadableStreams(
        streams.readableStreamFromReader(p.stdout),
        streams.readableStreamFromReader(p.stderr),
    ).pipeTo(new WritableStream({
        write(chunk): void {
            for (const line of decoder.decode(chunk).split(/\r?\n/)) {
                console.error(`[ ${cmd[0]} ] ${line}`)
            }
        },
    }))