The JS runtimes
Published in

The JS runtimes

Run a child process or execute shell commands in Deno

In the past, Deno has taken multiple shots at creating APIs for running child process or run shell commands & optionally collect the output. There is an API called Deno.run. There was another one called Deno.spawn. Recently, Deno has introduced another API called Deno.Command. This is the latest attempt on designing a versatile API for creating any child process. The Deno.Command API obsoletes Deno.spawn API. The Deno.Command API is still under unstable umbrella. In this article, we’ll see examples of Deno.Command API to create a sub process for:

Basic Usage

The Deno.Command API needs to be used with the new keyword. The C in Command is capital. The child process can be awaited to completion using async output API or sync outputSync API. The outputSync API blocks the event loop as expected.

// async version

const proc = await new Deno.Command("cmd", { args: ['1', '2'] }).output();

// sync version

const proc = new Deno.Command("cmd", { args: ['1', '2'] }).outputSync();

The basic usage of Deno.Command API is:

The output (i.e. the proc variable in the sample code) will be available when the child process finishes. The output contains:

// A sample of proc

{ success: true, code: 0, signal: null, stdout: [Getter], stderr: [Getter] }

The stdout and stderr are returned in raw format: Uint8Array. The returned buffer can be converted to string using TextDecoder APIs.

Regarding permissions, running a child process is subject to — allow-run permissions.

With this basic info, let’s jump over to some use-cases.

Running a child Deno process

To run a child Deno process, the exec should be the path to the Deno executable (available through Deno.execPath API). As Deno needs subcommands, run and child TS file is passed through args object.

child.ts

console.log("STANDARD OUTPUT FROM CHILD");
console.error("STANDARD ERROR FROM CHILD");

parent.ts

const td = new TextDecoder();
const p = await new Deno.Command(Deno.execPath(), { args: ["run", "child.ts"] })
.output();
const out = td.decode(p.stdout).trim();
const err = td.decode(p.stderr).trim();
console.log("STDOUT ==> ", out);
console.log("STDERR ==> ", err);

The output is as follows:

> deno run --allow-run --allow-read --unstable app.ts 
STDOUT ==> STANDARD OUTPUT FROM CHILD
STDERR ==> STANDARD ERROR FROM CHILD

Running a child Node.js process

To run a child Node.js process, the exec should be the path to the Node.js executable. The path has to be specified unless the node executable is present in the path of the running user. The only input required to Node.js child process is the child file. The same child.ts is used in this use-case too.

parent.ts

const td = new TextDecoder();
const p = await new Deno.Command("node", { args: ["child.ts"] })
.output();
const out = td.decode(p.stdout).trim();
const err = td.decode(p.stderr).trim();
console.log("STDOUT ==> ", out);
console.log("STDERR ==> ", err);

The output is as follows:

> deno run --allow-run --allow-read --unstable app.ts 
STDOUT ==> STANDARD OUTPUT FROM CHILD
STDERR ==> STANDARD ERROR FROM CHILD

Running a shell command

Running a shell command needs the command name as the first argument, followed by optional args. The following code runs ls -l in /var/tmp.

const td = new TextDecoder();
const p = await new Deno.Command("ls", { args: ["-l", "/var/tmp"] })
.output();
const out = td.decode(p.stdout).trim();
const err = td.decode(p.stderr).trim();
console.log("STDOUT ==> ", out);
console.log("STDERR ==> ", err);

The output is as follows:

> deno run --allow-run --allow-read --unstable app.ts 
STDOUT ==> total 248
-rw-r--r-- 1 mayankc wheel 5649 Dec 5 15:21 a.sh
-rw-r--r-- 1 _windowserver wheel 81920 Dec 5 13:29 cbrgbc_1.sqlite
-rw-r--r-- 1 _windowserver wheel 32768 Dec 5 13:29 cbrgbc_1.sqlite-shm
-rw-r--r-- 1 _windowserver wheel 0 Dec 5 13:29 cbrgbc_1.sqlite-wal
drwxr-xr-x 2 root wheel 64 Dec 5 13:29 kernel_panics
drwxr-xr-x 281 mayankc wheel 8992 Jan 2 12:54 perfResults
-rwxr-xr-x 1 mayankc wheel 54 Dec 14 22:24 someScript.sh
STDERR ==>

Running arbitrary shell script

An arbitrary shell script either needs a full path or should be present in the path of the current user. Again, optional command-line args can be passed through the args object.

The following example runs a script called someScript.sh, that prints the first argument on stdout and second argument on stderr.

someScript.sh

echo "Hello world to $1"
echo "Hello world to $2" >&2

parent.ts

const td = new TextDecoder();
const p = await new Deno.Command(
"/Users/mayankc/Work/source/denoExamples/someScript.sh",
{ args: ["Mayank", "Choubey"] },
)
.output();
const out = td.decode(p.stdout).trim();
const err = td.decode(p.stderr).trim();
console.log("STDOUT ==> ", out);
console.log("STDERR ==> ", err);

The output is as follows:

> deno run --allow-run --allow-read --unstable app.ts 
STDOUT ==> Hello world to Mayank
STDERR ==> Hello world to Choubey

More articles on similar topics can be seen in the magazine: The JS runtimes.

--

--

Articles on the popular JS runtimes, Node.js, Deno, and Bun

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store