Dshell is a new library and development environment for building console apps using Dart.

Dshell runs on Linux, MacOS and Windows.

Dart you say, what is this Dart thing?

Dart is a relatively new programming language from Google with a Java/JavaScript heritage with the bad bits taken out. Dart is becoming a mainstream language, if your interested why, google flutter.

If you are looking to learn Dart, dshell is an easy way to get started.

But I digress, let’s get back to talking about Dshell.

Dshell was designed to make building cli scripts and even whole cli apps a joy.

So dshell starts with Dart and then adds a library of commands that mimic many of the operations of bash. This combination creates the perfect building blocks for a cli script.

Before we get started, a quick word about developing with Dart and specifically with dshell.

When developing scripts we often don’t have a GUI so an IDE isn’t always available. With that in mind dshell is a pure cli application so you can easily develop dshell scripts using Vi or whatever other cli editor you prefer. Having said that, I recommend using an IDE whenever it’s available. Syntax highlighting, auto complete and automatic insertion of import statements will seriously improve your productivity.

My recommendation for an IDE is Visual Code. It’s lightweight, supported by Google and does a nice job of getting the job done.

I’ve included a link at the bottom of this article regarding installing Visual Code.

But let’s dive in and look at some examples of dshell.

If you would like you can work through examples as we go by installing dshell.

Start by installing Dart:

https://dart.dev/get-dart

Now install dshell:

pub global activate dshell

dshell install

You’re now ready to start developing with dshell.

Let’s have a look at an example dshell script.

hello_world.dart

To run the above script:

run hello_world.dart

The first time you run a script, dshell needs to do some housekeeping. Dshell creates a virtual project and downloads any dependencies that your script requires.

Don’t worry about the start time of your script, the second time you run the script the start time will be much faster. If you need serious speed dshell can compile your script to a native executable. But more about that in part 2.

Let’s pull the code apart line by line.

Line 1 is called a ‘shebang’. It essentially tells your OS that the script is to be executed with dshell. You should include this in all your dshell scripts.

Line 2 imports the dshell package making all of its yummy goodness available.

Line 4 the entry point for the script.

Line 5: we print ‘hello world’

Rather than creating a script yourself let dshell do it for you.

Type: dshell create hellow.dart

dshell create

The ‘dshell create’ command creates a sample ‘hello world’ dart script, does all the required housekeeping and marks the script as executable.

going forward dshell will ship with a number of starter templates that provide common starting points for writing scripts.

Calling external app

One of bash’s super powers is that it can call any external application. Well so can dshell.

Line 5 of the above example runs the grep command. Any output from grep is written directly to the console.

For Dart users this code may look a little confusing. How do you run a String? Dshell uses the Dart 2.7 ‘extensions’ feature to extend the set of String methods. In this case we have added a ‘run’ getter. Watch out for additional String overloads in the examples below.

Using forEach

We can ‘run’ a command but how do we process the output. Simple, we use the ‘forEach’ method:

Dart supports anonymous functions (or more broadly lambda’s and closures) just as Javascript and Java does. The above forEach function takes a lambda.

In line 5 the lambda is the part:

(line) => print(line)

Essentially ‘(line)’ is an argument passed to the lambda. This is essentially the ‘method signature’ of the anonymous function. Each time grep outputs a line, the forEach method calls the above lambda.

The ‘line’ argument will contain the line output by grep to stdout.

The => operator (sometimes referred to as a ‘fat arrow’) tells us that this lambda is expecting a single expression on the right hand side of the => operator. In this case we pass the line argument to the print statement which prints the line to the console.

In case you are not familiar with ‘stdout’ every cli app has three file handles passed to it by the OS. stdin, stdout, and stderr. Stdin can be read and contains any data piped to the app, the Dart print command writes to stdout (which normally goes to the console) and stderr is where an cli app should write any error messages to.

Lets now compare line 5 to line 7.

Line 5: … .forEach((line) => print(line));

Line 7: … .forEach((line) { print(‘matched $line’); });

What we are seeing here are the two forms of a Dart lambda.

Line 5 uses the fat arrow which expects a single expression.

Line 7: drops the fat arrow in exchange for a statement block ‘{}’.

The advantage of the statement block is that you can include multiple statement lines (each terminated by a semi-colon).

The second point of interest in line 7 is the use of ‘$line’ in the print statement. This is a Dart string feature. You can insert any variable into a string by preceding it with a ‘$’. You can in fact include any expression by encapsulating the expression with ${}. e.g. print(‘${line.substring(3)}’).

Line 9 now gets a little more interesting:

… .forEach( (line) => print(line) ,stderr:(line) => print(red(line)));

So what’s going on here? The first half of the line is recognisable from line 5, but what about the second half:

stderr:(line) => print(red(line))

Some of this makes sense:

(line) => print(red(line))

This looks just like the lambda we previously used to print lines to the console. You can probably guess that the call to ‘red(line)’ changes the color of the line written to the console to red.

dshell provides a number of functions for applying color to text by using the ansi terminal escape sequences. We plan on expanding the support for ansi terminals by including cursor positioning commands and field editors.

But what is the ‘stderr:’ all about?

When you run a command like grep, it outputs any text to stdout, but if an error occurs it writes the error message to stderr. When we used the ‘.run’ method with grep both stdout and stderr are written to the console. But when we use forEach as we did in Line 5 and 7 we are only writing stdout to the console and essentially suppressing stderr (just like sending it to /dev/null).

Suppressing stderr is not such a good idea but often convenient so forEach lets us ignore stderr. If we want or need to process stderr then that’s where the ‘stderr:’ syntax comes in.

If you are a Dart programmer you will immediately realise that this is a named parameter. But for the non-Dart programmers let’s stop for a moment and explain named parameters.

Dart functions and methods support three types of arguments.

Positional, optional and named.

When declaring a named argument in a Dart method we use braces to designate it. So the signature of the forEach method is:

forEach(LineAction stdout, {LineAction stderr});

When calling a function with a named argument we use the name and a colon. e.g.

forEach((line) => print(line), stderr: (line) => print(line));

The first positional argument is:

(line) => print(line)

The second named argument is:

stderr: (line) => print(line)

Named arguments are optional which is why we could write Line 5 without mentioning stderr.

forEach is probably one of the most important methods in dshell as we use it repeatedly to process lines of data.

Let’s talk about piping.

Another great feature of bash is its ability to call multiple applications and ‘pipe’ the data from one application to the next. Well, we can do the same with dshell.

(‘grep error /var/log/syslog’ | ‘head -n 5’ | ‘tail -n 1’.)forEach((line) => print(‘The fifth error is: $line’);

The above line calls grep to find all the lines containing ‘error’, passes the results to the ‘head’ command which outputs just the first 5 lines, then tail outputs just the last line of those five and finally we use dart to print the fifth line.

Built in commands

Dshell provides an swiss army knife of built in functions for building cli scripts.

For bash users; Dart supports top level functions just as bash does however unlike bash, the functions can be in any order. Dart also supports classes.

find(‘*.png’, recursive: false).forEach((line) => print(line);

Find is one of the many built-in commands that ships with dshell. By default find does a recursive search from the current directory but in this case we only want to search the current directory so we pass the optional named argument ‘recursive’ with a value of false. Once again we see the the forEach method in use to process each of the filenames returned by find.

Alternatively, we may want to store the found png files in a list. The dshell commands that support forEach also supports ‘toList’ so if we want to save the list of png files we can simply do:

var files = find(‘*.png’).toList() ;

To create a directory we do:

createDir(‘/home/me/a/path/to/far’, recursive: true);

The optional, named argument ‘recursive’ tells dshell to create any intermediate paths that don’t already exist.

To ask the user a question:

var answer = ask(prompt: ‘Y/N’);

To delete a file:

delete(‘notneeded.txt’);

To move a file:

move(‘from path’, ‘to path’);

To check if a file or directory exists:

if (exists(‘/home/does/it/exist’)) {print(‘found it’)’;

You can access environment variables with the ‘env’ function:

var username = env(‘USERNAME’);

dshell also directly exposes some environment variables such as:

HOME — your home directory PATH — a String array containing the paths on your PATH.

There are many more builtin commands which you can read about on the pub.dev site:

https://pub.dev/packages/dshell

The full list of library functions is at:

https://pub.dev/documentation/dshell/latest/dshell/dshell-library.html

Paths, Paths and more Paths

When writing CLI scripts you tend to spend a lot of your time manipulating directory paths. Dshell makes this easy by including the neat ‘path’ package.

The path package provides a set of global functions that allow you to create and manipulate directory paths.

Some of the most commonly used are:

join(‘/home’, ‘my’) == ‘/home/my’ canonicalize(‘/home/../home’) == ‘/home’ absolute(‘test’) == ‘/home/me/test’

You can see additional details on the path package at:

https://pub.dev/packages/path

Packages, packages and more packages

One of dshell’s great strengths is how easy it is to extend dshell. The Dart eco-system includes hundreds (if not thousands) of packages that provide all sorts of functions. I will leave package management for a future article but you can read more on the dshell page if you want to dig into how we manage packages via a virtual pubspec.yaml at:

https://pub.dev/packages/dshell

Accepting Arguments

To make your CLI script useful you will more than likely want to process arguments passed to your script. Dart is similar to C and Java in that its entry point is called ‘main’ and it takes an array of arguments.

Line 2 imports Dart’s io library so we can use the ‘exit’ function below.

Line 5 shows us the main entry point. The main method returns void which means we need to use the ‘exit’ function to return an exit code from the script.

Line 5 also declares that main takes a List of Strings called ‘args’. Dart supports Generics. I’ve provided some references at the bottom of this article on Dart and using Generics with Dart.

Line 7 prints the no. of arguments passed. Again we are using the Dart ‘$’ notation that lets us embed variables into Dart.

Summary

There is a lot more to dshell but I think you can see by the above examples that dshell is a simple and expressive method of writing cli scripts.

You should now have enough information to start writing basic cli scripts using dshell.

Well I think that’s enough for one day.

In part 2 will build a complete cli app using dshell.

I would love to get feedback on this article and dshell in general.

You can write a response to this medium article or raise an issue on the dshell github project.

https://github.com/bsutton/dshell

I’m also looking for collaborators or you could write your own article about dshell :)

Regards,

Brett

References:

Dart packages: https://pub.dev

Note you can NOT use packages designed for Flutter or or Web.

Dshell: https://pub.dev/packages/dshell

Dshell git repo: https://github.com/bsutton/dshell

Path: https://pub.dev/packages/path

Args: https://pub.dev/packages/args

Dart Language Tour: https://dart.dev/guides/language/language-tour

A great getting started guide for dart.

Generics: https://www.tutorialspoint.com/dart_programming/dart_programming_generics.htm

An overview of Dart Generics.

Visual Code: links to installing visual code an installing the required extensions.

https://dartcode.org/