Replace Bash With Deno

Cody Casterline

Replace Bash With Deno

I'm starting to get a reputation as a bit of a Deno fanatic lately. But (if you haven't seen the title of this blog post) it might surprise you why I'm such a fan.

If you visit deno.com, the official documentation will tell you things like:

  • "Web-standard APIs"
  • "Best in class HTTP server speeds"
  • "Secure by default."
  • "Hassle-free deployment" with Deno Deploy

While all of those are great features, in my opinion the most underrated feature of Deno is that it's a great replacement for Bash (and Python/Ruby!) for most of your CLI scripting/automation needs. Here's why:

Deno is not Bash

Bash is great for tossing a few CLI commands into a file and executing them, but the moment you reach for a variable or an if statement, you should probably switch to a more modern programming language.

Bash is old and has accumulated a lot of quirks that not all programmers will be familiar with. Instead of removing the quirks, or warning about them, they're kept to ensure backward compatibility. But that doesn't make for a great programming language.

For example, a developer might write code roughly like:

if [ $x == 42 ]; then 
    echo "do something"
else
    echo "do something else"
fi

Can you spot the problems?

  • if $x is undefined, the test expression will fail with an error.
  • if $x is a string that includes spaces and/or a ] character, the tester will likely return an error.
  • Neither above error stops the script from executing. It is equivalent to the test expression returning "false". You're just going to have unexpected behavior in the rest of your script.
  • Worse, some values of $x may silently return false positives for this match. (I leave crafting them as an exercise to the reader. Share your favorites!)

These gotchas are even more dangerous when you're writing a script to manage files. Several versions of rm now have built-in protections against accidentally running rm -rf / because it is such a common mistake you can make in Bash and other shells when your variable expansion goes awry.

Do you need an array? As recently as a couple years ago (and possibly even still?) the default version of Bash on MacOS is old enough to not support them. If you write a bash script that uses arrays, you'll get different (wrong) behavior on MacOS.

Seriously, stop writing things in Bash!

Single File Development

My theory is that Bash scripts are the default because people want to just write a self-contained file to get a thing done quickly.

Previously, Python was what I would reach for once a task became unwieldy in Bash. But, in Python you might need to include a requirements.txt to list any library dependencies you use. And if you depend on particular versions of libraries, you might need to set up a venv to make sure you don't conflict with the same libraries installed system-wide at different versions. Now your "single-file" script needs multiple files and install instructions.

But in Deno you can include versioned dependencies right in the file:

import { range } from "https://deno.land/x/better_iterators@v1.3.0/mod.ts"

for (const value of range({step: 3}).limit(10)) {
    console.log(value)
}

There is no install step for executing this script. (Assuming your system already has Deno.) The first time you deno run example.ts, Deno will automatically download and cache the remote dependencies.

You can even add a "shebang" to make the script executable directly on Linux/MacOS:

#!/usr/bin/env -S deno run

import ...

While Windows doesn't support shebang script files, the deno install command works on Windows/Linux/MacOS to install a user-local script wrapper that works everywhere.

image.png

Not only that, you can deno install and deno run scripts from any URL!

deno run https://deno.land/x/cliffy@v0.25.7/examples/ansi/color_themes.ts

But wait, there's more!

Deno makes TypeScript a first-class language instead of an add-on, as it is in Node.js, so the file you write is strongly typed right out of the box. This can help detect many sorts of errors that Bash, Python, Ruby, and other scripting languages would let through the cracks.

By default, Deno leaves type checking to your IDE. (I recommend the Deno plugin for VSCode.) The theory is that you've probably written your script in an IDE, so by the time you deno run it, it would be redundant to check it again. But, if you or your teammates prefer to code in plain text editors, you can get type-checking there as well by updating your shebang:

#!/usr/bin/env -S deno run --check

Now, Deno will perform a type check on a script before executing it. If the check fails, the script is never executed. This is much safer than getting half-way through a Bash or Python script and failing or running into undefined behavior because you typo'd a variable name, or had a syntax error.

image.2.png

Don't worry, the results of type checks are cached by Deno, so you will only pay the cost when the file is first run or modified.

Summary

While I have not been a fan of JavaScript in the past, Deno modernizes JavaScript/TypeScript development so much that I find myself very productive in it. It's replaced Bash and Python as my go-to scripting language. If you or your team are writing Bash scripts, I'd strongly recommend trying Deno instead!