Create your own Deno utils with pipelines support

Steven Chim
4 min readMay 23, 2020

Once you discover Unix pipelines, you’ll know they can be extremely powerful. Here’s just a simple example to list a folder and filter on anything with an M.

ls | grep M

Write once, maintain never

When you start writing your own shell scripts sometimes it feels a bit like: “Write once, maintain never.” Especially when you don’t write shell scripts occasionally. You’ll need to add a loads of comments for your future self, just so can you read your own code…

Example from: https://fortes.com/2019/bash-script-args-and-stdin/

# Copy command-line arguments over to new array
ARGS=( $@ )

# Read in from piped input, if present, and append to newly-created array
if [ ! -t 0 ]; then
readarray STDIN_ARGS < /dev/stdin
ARGS=( $@ ${STDIN_ARGS[@]} )
fi

# Single loop to process all arguments
for ARG in "${ARGS[@]}"; do
echo "$ARG"
done

Pipelines with Deno stdin

With Deno, you can now have a webby experience; which means no more centralised package manager. Just like the old web days; Import a dependency from an URL and you’re ready to go. Together with first class support for TypeScript you’ll get a really nice experience out of the box.

Here is a minimal Deno example to pipe data to into a Deno script. This script will just prefix “Hello, ” and output the result.

// hello.tsimport { readLines } from "https://deno.land/std/io/bufio.ts";// Detect piped input
if (!Deno.isatty(Deno.stdin.rid)) {
for await (const line of readLines(Deno.stdin)) {
line && console.log(`Hello, ${line}`);
}
}

To run this script:

echo "Deno" | deno run hello.ts And this will output “Hello, Deno

You can also use an file as input; Let say you have a text file names.txt

Deno
TypeScript

When you run deno run hello.ts < names.txt. It will output “Hello, Deno” and “Hello, TypeScript

Merge pipe and arguments

Inspired by: https://fortes.com/2019/bash-script-args-and-stdin/

The Deno example below is a port of the bash script to merge pipe and arguments. It allows your script to be used with pipes or use arguments to provide input data.

import { readLines } from "https://deno.land/std/io/bufio.ts";const list: string[] = [];// Detect piped input
if (!Deno.isatty(Deno.stdin.rid)) {
for await (const line of readLines(Deno.stdin)) {
line && list.push(line);
}
}
// merge pipe and args
[...list, ...Deno.args].forEach((item) => {
console.log(`Hello, ${item}`);
})

Your script now also accepts arguments as input:

deno run hello.ts "Deno"

For more control on arguments see: https://deno.land/std/flags/README.md

Webby Deno

Because of Deno’s webby nature. You can just put your script anywhere on the web and run execute it directly from there.

echo "Deno" | deno run https://gist.githubusercontent.com/chimurai/0c4327f708e60528d407f286730356b8/raw/225a7977c769507cdd761bb1a7f830fdbe04f2ed/hello.ts

If you want to “shout” and UPPERCASE the input. Just pipe it through another script:

echo "Deno" | deno run https://gist.githubusercontent.com/chimurai/0c4327f708e60528d407f286730356b8/raw/225a7977c769507cdd761bb1a7f830fdbe04f2ed/hello.ts | deno run https://gist.githubusercontent.com/chimurai/0c4327f708e60528d407f286730356b8/raw/fdca52a4912b08ddb9d8b7e0278040d7058a1fa4/shout.ts

Deno executables

If you don’t want to deal with the long urls; Install the Deno script as executable.

Install executable

Install the scripts and optionally rename the executable with the--name flag:

deno install --name hello https://gist.githubusercontent.com/chimurai/0c4327f708e60528d407f286730356b8/raw/225a7977c769507cdd761bb1a7f830fdbe04f2ed/hello.tsdeno install --name shout https://gist.githubusercontent.com/chimurai/0c4327f708e60528d407f286730356b8/raw/225a7977c769507cdd761bb1a7f830fdbe04f2ed/shout.ts

Configure $PATH

Scripts are installed in this folder: ~/.deno/bin

Add ~/.deno/bin to PATH (add it to your~/.bashrc or ~/.zshrc)

export PATH="$HOME/.deno/bin:$PATH"

Reload your terminal with source ~/.bashrc or source ~/.zshrc or start a fresh one.

Give it a try

You should be able to run this:

echo "Deno" | hello | shout

And it will output: HELLO, DENO!

After thoughts

Deno’s webbyness and first class TypeScript support is awesome! This vanilla experience will instantly make you feel like home.

Rediscovering Unix Pipelines. As functional programming and RxJS usage is quite common in JavaScript and TypeScript development, you can reuse the same paradigm to author tooling for your shell environment. The scripts will be quite simple and composable. Hopefully this will prevent the tendency for scripts to become a monolithic nightmare.

As the Deno ecosystem grows I can only imagine what the possibilities will be.

Now, which one do you prefer?

Before with: Bash

# Copy command-line arguments over to new array
ARGS=( $@ )

# Read in from piped input, if present, and append to newly-created array
if [ ! -t 0 ]; then
readarray STDIN_ARGS < /dev/stdin
ARGS=( $@ ${STDIN_ARGS[@]} )
fi

# Single loop to process all arguments
for ARG in "${ARGS[@]}"; do
echo "$ARG"
done

After with: TypeScript

import { readLines } from "https://deno.land/std/io/bufio.ts";const list: string[] = [];// Detect piped input
if (!Deno.isatty(Deno.stdin.rid)) {
for await (const line of readLines(Deno.stdin)) {
line && list.push(line);
}
}
// merge pipe and args
[...list, ...Deno.args].forEach((item) => {
console.log(`${item}`);
})

--

--