18

I want to run any arbitrary bash command from Deno, like I would with a child_process in Node. Is that possible in Deno?

Evan Conrad
  • 3,993
  • 4
  • 28
  • 46

5 Answers5

17

Deno 1.28.0 added a new API to run a shell command: Deno.Command (--allow-run permission required)

let cmd = new Deno.Command("echo", { args: ["hello world"] });
let { code, stdout, stderr } = await cmd.output();
// stdout & stderr are a Uint8Array
console.log(new TextDecoder().decode(stdout)); // hello world

More advanced usage:

const command = new Deno.Command(Deno.execPath(), {
  args: [
    "eval",
    "console.log('Hello World')",
  ],
  stdin: "piped",
  stdout: "piped",
});
const child = command.spawn();

// open a file and pipe the subprocess output to it.
child.stdout.pipeTo(
  Deno.openSync("output", { write: true, create: true }).writable,
);

// manually close stdin
child.stdin.close();
const status = await child.status;

const s = await c.status;
console.log(s);

This API replaced the deprecated Deno.run


OLD ANSWER:

In order to run a shell command, you have to use Deno.run, which requires --allow-run permissions.

There's an ongoing discussion to use --allow-all instead for running a subprocess


The following will output to 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();

If you want to store the output, you'll have to set stdout/stderr to "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();
Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98
9

Make sure to await status or output of the child process created with Deno.run.

Otherwise, the process might be killed, before having executed any code. For example:

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);

Pass arguments via 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);

There is also a good example resource in Deno docs.

Community
  • 1
  • 1
ford04
  • 66,267
  • 20
  • 199
  • 171
4

You can do that with the run like this:

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

You'll have to --allow-run when running the script in order for this to work:

deno run --allow-run ./myscript.js
joe
  • 3,752
  • 1
  • 32
  • 41
Evan Conrad
  • 3,993
  • 4
  • 28
  • 46
1

If your shell command prints out some messages before the process is about to end, you really want pipe stdin and stdout to your own streams and also throw an exception which you can catch.

You can even alter the output while piping the process streams to your own streams:

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,
    }
}

If you don't have different logic for each writable stream, you can also combine them to one:

    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}`)
            }
        },
    }))
Martin Braun
  • 10,906
  • 9
  • 64
  • 105
0

Alternatively, you can also invoke shell command via task runner such as drake as below

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
Isaac
  • 12,042
  • 16
  • 52
  • 116