Introducing crsh: a Javascript based command line shell

12 July 2020

TL;DR: GitHub link -> Install instructions


Existing shells generally operate with their own somewhat archaic syntax, continuous with the underlying operating system and set of historical design choices.

This puts the majority of shell syntax just out of reach for a very large subset of command line users who use the command line for little more than launching one or two command line tools, navigating directories, and maybe ocassionally piping commands or outputting them to files. These users will usually write scripts in a language more familiar to them as soon as things get a little trickier (because the value of learning the control flow syntax in bash, zsh, or fish does not seem like time well spent).

This is not a criticism of those shells, exactly. But it does highlight a potential space for new shells that can better fulfill the needs of these users. Conversely, if you are an experienced bash shell scripter, and terms like “file descriptors” and SIGCHLD mean something to you, you will probably dislike my approach here. But please feel free to stick around and provide some advice.

Enter crsh

I’ve started work on a shell that can seamlessly interoperate Javascript syntax (and in fact works by evaling JS in the Deno runtime) and traditional shell execution.

Regular command execution will be familiar to those who have used the command line:

$ ls | grep .js
builtins.js
dsh.js
functions.js

And inline anonymous functions will be familiar to those who have used Javascript:

$ () => "Hello world!"
Hello world!

Returning a list from an inline function will be outputted as separate lines:

$ () => new Array(5).fill().map((line, index) => `Line ${index}`)
Line 0
Line 1
Line 2
Line 3
Line 4

Combining these concepts can yield a very expressive shell:

$ ls | ({ lines }) => lines.map((line, index) => `line ${index}: ${line}`) | grep line 3
line 3: functions.js

Note in the above that lines is made available to piped inline functions. JSON output piped into an inline function is automatically parsed and made available as a json parameter:

$ curl https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49 | ({ json }) => json.title
My Neighbor Totoro

As can be seen, such a shell lends itself very well to parsing JSON (it is Javascript Object Notation after all).

To go one step further in this example, we can navigate multi-request JSON API calls in a human-readable way:

$ curl https://ghibliapi.herokuapp.com/films/ | ({ json }) => json[0].people | xargs curl | ({ json }) => json[0].name

While the above may look less readable than something like jq, those with a Javascript background will appreciate the transferability of knowledge. Even without such a background, the advantage of executing arbitrary logic inline with other shell calls in a modern language should be apparent.

Configuration

Currently, the easy parts to configure are the prompt and auto-completion rules. This is done by way of editing the JS source directly. The prompt is specified as a function which returns a string, and exported from a prompt.js file like so:

import { magenta, stripColor } from "https://deno.land/std/fmt/colors.ts";

export const prompt = () => {
  const currentDir = magenta(Deno.cwd());
  return `${currentDir} › `;
};

export const promptLength = () => stripColor(prompt()).length;

And the auto-completion rules are specified as a list of objects representing the regex to match the input on, and the logic to return the list of completion to cycle through:

const gitRules = [
  {
    match: /^git add /,
    complete: completeFile,
  },
  {
    match: /^git checkout /,
    complete: completeBranches,
  },
  {
    match: /^git branch -D /,
    complete: completeBranches,
  },
  {
    match: /^git /,
    complete: completeCommands
];

Community-made auto-completion rules and prompts could be hosted in the usual Deno way.

Future work

There is still a lot to do to make this better. Bug fixes, features like multiline support, persisting history, etc. Any feedback is welcome in the form of issues in the GitHub repo.

← Simple Synth All posts