A Lisp Interpreter for Linux Shell Scripting
Content
Motivation
A few weeks ago, I started learning about Lisp and decided to write my own Lisp interpreter, mostly to understand the language and it’s concepts better. Once I got the interpreter working, I natually wanted to do something useful with it. But writing a full-scale project entirely in my own Lisp sounds like a recipe for disaster!
Then I thought about smaller shell scripts, which I often need to write for work or personal projects. Could I implement shell scripting features in my Lisp - things like executing shell commands, piping, and capturing output?
Well, as it turns out, the answer is yes!
You can find the source on GitHub and try it out yourself! Please be aware this project is still very much in alpha, so there will be bugs.
What is Lisp?
Lisp is one of the oldest high-level programming languages, originating in the
late 1950s. It’s known for its distinctive use of parentheses, prefix notation,
and S-expressions, which elegantly represent both code and data. Although Lisp
has fallen somewhat out of favor in recent decades, it pioneered many
foundational programming concepts - such as garbage collection - and has
profoundly influenced countless modern languages. In fact, functions such as
map
, filter
and reduce
were all introduced by Lisp.
Using Lisp
Getting Started
My Lisp supports the classic Lisp features, such as functions, lambdas, closures and even powerful macros.
; add 2 and 3
(+ 2 3)
; print to terminal
(defvar my-name "Jakob")
(println "Hello " my-name)
; define a function
(defun sq (x)
(* x x))
; apply the function 'sq' to every element of the list
(map sq (list 1 2 3 4))
; => (1 4 9 16)
; returns a list of all elements larger than 3
(filter (lambda (x) (> x 3)) (list 1 2 3 4 5))
; => (4 5)
Shell Scripting
My Lisp doesn’t just stop at the traditional language features, it also comes with some neat tools for shell scripting. This means you can mix the elegance of Lisp with the power of your Linux command line.
Here are a few examples to show what it can do:
; pipe output of 'ls' to 'grep'
(pipe (sh ls -la) (sh grep "*.txt"))
; save the output of 'cat' to variable 'content'
(defvar content ($ (sh cat "my-file.txt")))
; pipe the output of the function 'hello' into the 'rev' command
(defun hello (name)
(strcat "Hello " name "!"))
(pipe (<<< (hello "Jakob")) (sh rev))
; => !bokaJ olleH
; eqivalent to 'sudo apt update && sudo apt upgrade -y' in bash
(and (sh sudo apt update) (sh sudo apt upgrade -y))
; define a function that uses 'scp' to upload some files to a server
(defun upload (source)
(let ((user "root")
(host "example.com")
(target-dir "/var/www/files/")
(target (strcat user "@" host ":" target-dir)))
(sh scp source target)))
(upload "my-file.txt")
Because everything is a Lisp expression, you can easily combine, nest, or even generate shell commands programmatically. For example, you could build dynamic command pipelines or iterate over files, all without ever leaving Lisp syntax.
By adding the shebang
#!/usr/bin/lisp
to the top of your lisp scripts and by making them executeable
with chmod +x my-script.lsp
, you can run your Lisp program just like any other
shell script.
Try it yourself!
You can easily build lisp from source by cloning the GitHub repo or, if you are brave enough, install the pre-built binary:
bash <(curl -s https://raw.githubusercontent.com/gue-ni/redstart/refs/heads/master/tools/install.sh)
You can also find a more detailed reference at the repo.