Skip to content

Internal DSLs for Shell

Spencer Baugh edited this page Oct 12, 2023 · 21 revisions

Internal Shell DSLs in Various Languages

Every language seems to have an internal DSL for shell commands. This approach is probably OK for small things, but I haven't seen it in the wild in major pieces of software.

Python

plumbum in Python -- The motto of the library is "Never write shell scripts again", and thus it attempts to mimic the shell syntax ("shell combinators") where it makes sense, while keeping it all Pythonic and cross-platform.

python-mario -- Have you ever wanted to use Python functions directly in your Unix shell? Mario can read and write csv, json, and yaml; traverse trees, and even do xpath queries. Plus, it supports async commands right out of the box.. This looks a bit like perl -e for Python. That is, you're using Python from shell, not embedding shell-like code inside Python.

sh in Python -- sh is a full-fledged subprocess replacement for [multiple Python versions] that allows you to call any program as if it were a function. I wouldn't call this a shell because it doesn't support pipelines and such, but it's an example of programmers preferring the syntax of their language to the syntax of Unix shell.

pysh -- Dormant project where the author encountered problems in the approach: I no longer believe this approach to shell scripting to be a good solution. pysh's approach is to modify the syntax of python resulting in an uglier, and confusing, language. Maybe someday I'll stumble upon the ``right way'' to implement a shell language, but for now bash is just fine. (Many years later, I don't agree bash is fine!)

zxpy zx-inspired shell scripting tool in Python.

aush - aush is a Python library to make calling other programs and getting their output as easy as possible. It's intended to be the best possible melding of shell scripting and Python

duct.py - Duct is a library for running child processes. Duct makes it easy to build pipelines and redirect IO like a shell. At the same time, Duct helps you write correct, portable code. There's some nice documentation of gotchas of running processes, and how duct.py avoids them

(Feedback on lobste.rs on the differences between these libraries)

Lisp-y Languages

EShell in Emacs Lisp -- A bash-like shell embedded in Emacs. Example Syntax, Mastering EShell.

scsh in Scheme -- Scsh has two main components: a process notation for running programs and setting up pipelines and redirections, and a complete syscall library for low-level access to the operating system. Oil also aims to have a complete syscall library.

inferior-shell in Common Lisp -- This CL library allows you to spawn local or remote processes and shell pipes. It lets me use CL in many cases where I would previously write shell scripts.

janet-sh: DSL/library for concisely running shell commands in Janet. Supports pattern matching on the exit codes of each command in a pipeline.

closh -- Bash-like shell based on Clojure. This may have some of its own syntax, but it also uses Clojure syntax.

rash -- Racket #lang for shell scripting and interaction. Allows pipelines to mix processes and Racket functions, has user-definable pipeline operators, lets you embed normal Racket and shell-style Rash code within each other, and inherits all of Racket's features.

scsh-process -- chicken scheme

Other Dynamic Languages

Julia —- like Ruby, it has backticks syntax. Unlike Ruby, backticks return a command object, so no escaping of arguments is necessary.

zx in node.js, using async/await. zx package provides useful wrappers around child_process, escapes arguments and gives sensible defaults. JavaScript backticks make it possible to properly escape arguments.

vl -- shell scripting in typescript. Powered by deno. Started out as a port of zx.

Shell in Ruby -- It provides users the ability to execute commands with filters and pipes, like sh/csh by using native facilities of Ruby. This is in the standard library? Ruby also has build-in backticks syntax for executing shell commands (susceptible to injection vulnerabilities).

psh in Perl -- Perl Shell (psh) combines aspects of bash and other shells with the power of Perl scripting. This one is notable because Perl has a heavy influence from shell, sed, and awk. It appears it's still not close enough.

forsh in Forth -- forsh is a shell built on top of gforth. It allows one to easily operate a unix-like operating system without leaving the gforth environment

grub2 -- Shell + bootloader. What could possibly go wrong?

Statically Typed Languages

Ammonite-Ops in Scala -- a library to make common filesystem operations in Scala as concise and easy-to-use as from the Bash shell

HSH in Haskell -- HSH is designed to let you mix and match shell expressions with Haskell programs.

shh in Haskell -- Enables convenient shell-like programming in Haskell. Also wraps GHCi (Haskell's REPL) to act as an alternative shell. The README also discusses other, similar, projects in Haskell

Caml-Shcaml: an OCaml Library for Unix Shell Programming

janestreet/shexp -- Shexp is composed of two parts: a library providing a process monad for shell scripting in OCaml as well as a simple s-expression based shell interpreter. Both provide good debugging support.

xshell -- xshell makes it easy to write cross-platform "bash" scripts in Rust

Scripting With Go -- script is a Go library for doing the kind of tasks that shell scripts are good at: reading files, executing subprocesses, counting lines, matching strings, and so on. The author liked the elegant and concise nature of well-crafted shell pipelines, but liked Go even more.

Internal Awk DSLs

FuncShell – A Haskell-based alternative to awk -- Also has links to sqawk in SQL, luawk in Lua.

myawk: shell/AWK/Perl-like scripting in OCaml -- by Oleg. The library myawk is a small OCaml library for quick, rough and ready scripts. It is meant as a lightweight and less idiosyncratic replacement for AWK (let alone Perl) -- and also as an exploration and demonstration of a better alternative to shell pipes. Numerous familiar complaints about shell on this page too!

pyp -- Awk-like tool leveraging the Python interpreter. pyp will statically analyse the input code to detect undefined variables. Based on what it finds, it will proceed to transform the AST of the input code as needed. We then compile and execute the result, or if using --explain, unparse the AST back to source code. Has magic lines variable, etc.

Internal Build Tool DSLs

As with shells, each language community has explored idea of using their language to express build rules.

SCons in Python -- Configuration files are Python scripts--use the power of a real programming language to solve build problems

Rake in Ruby -- Rakefiles (rake's version of Makefiles) are completely defined in standard Ruby syntax. The book Beautiful Code has an essay by Matz which discusses why this is possible and nice in Ruby.

Jake in JavaScript -- A Jakefile is just executable JavaScript. You can include whatever JavaScript you want in it.

Grunt in JavaScript -- This is called a "task runner" rather than a build tool. A shell is also a task runner! The Gruntfile can execute arbitrary code and do I/O, i.e. read package.json files.

Shake in Haskell -- Shake is implemented as a Haskell library, and Shake build systems are structured as Haskell programs which make heavy use of the Shake library functions

sbt in Scala -- Scala-based build definition that can use the full flexibility of Scala code

Related

Clone this wiki locally