I want to run any arbitrary bash command from Deno, like I would with a child_process
in Node. Is that possible in Deno?
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();
/usr/bin/echo
–
Abuzz new Deno.Command("pwsh", { args: ["-c", "echo", "hello world"] });
–
Spellman 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.
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
--allow-all
flag there's --allow-run
–
Neckcloth 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}`)
}
},
}))
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/[email protected]/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
© 2022 - 2024 — McMap. All rights reserved.
echo: program not found
. Why is that? – Spellman