Unix in Lisp is currently a usable Lisp (not POSIX!) shell for Unix. The distinctive feature of Unix in Lisp is that rather than creating a sub-language for Unix operations, Unix concepts are directly/shallowly embedded into Lisp (Unix commands become Lisp macros, Unix file become Lisp variables, Unix streams become lazy Lisp sequences, etc).
The fact that Unix in Lisp is Lisp, rather than an interpreter written in Lisp, makes it possible to leverage existing tools for Lisp. One instance is the Unix in SLIME listener, which inherits completion, interactive debugger and multiple listeners etc from SLIME itself. Another instance is that existing CL library such as sequence helper functions from serapeum
works out of the box on Unix in Lisp process streams.
Currently, only SBCL is supported. Clone this repository into your ~/quicklisp/local-projects/
. Unix in Lisp is currently at alpha stage and will receive frequent changes. It’s recommended to use Ultralisp to install dependencies, to make sure various bug fixes to the upstream are available which Unix in Lisp relies on. If you haven’t done so, (ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)
. Before first-time use, run (ql:update-dist "ultralisp")
and (ql:quickload "unix-in-lisp")
to install all dependencies. It’s also advised to (ql:update-dist "ultralisp")
and git pull
this repo regularly to get updates and bug fixes.
It’s recommended to load unix-in-slime.el
for better SLIME integration. To load it, evaluate (require 'unix-in-slime "~/quicklisp/local-projects/unix-in-lisp/unix-in-slime")
in emacs. You may want to add this line to your init.el
. Then M-x unix-in-sime
to start a listener, and have fun!
unix-in-slime
installs hacks to the host Lisp environment by calling (unix-in-lisp:install)
on startup. To undo hacks done to the host environment and unmount Unix FS packages, run (unix-in-lisp:uninstall)
.
Examples
(Some print-outs are omitted)
Counting number of files
/Users/kchan> (cd quicklisp/local-projects/unix-in-lisp) /Users/kchan/quicklisp/local-projects/unix-in-lisp> (pipe (wc -l) (ls)) 9
But why not the Lisp way as well!
/Users/kchan/quicklisp/local-projects/unix-in-lisp> (length (package-exports ./)) 9
For more examples, see TUTORIAL.org
File System Mapping
Directories are mapped as Unix FS packages. A Unix FS packages is any Common Lisp package whose package name designate an absolute path name (usually when it starts with a slash).
The exported symbols of a Unix FS package should one-to-one correspond to files in the mapped directory. Exceptions to this one-to-one correspondence:
- Because of the limit of file system change tracking, the package structure in the Common Lisp image may diverge from the Unix FS state.
- Currently, the state of a Unix FS package is synchronized when calling
mount-directory
. By default,remount-current-directory
is added to*post-command-hook*
, which does the obvious thing.
- Currently, the state of a Unix FS package is synchronized when calling
Each of these exported symbols has a global symbol macro binding, so that they can be read/write like Lisp variables. Access to the symbol gives the list of lines of the underlying file, and setting it with a list designator of lines cause them to be written to the file.
unix-user /Users/kchan/> .bashrc ("export CC="clang"" "export PS1='$(hostname):$(pwd) $(whoami)\$ '" ...)
Note that there are no corresponding symbol for a non-existent file. To write or create a file that you are not sure whether it already exists, it’s recommended to use defile
macro, which will ensure the file exists and creates the corresponding symbol.
unix-user /Users/kchan/> (defile iota.txt (iota 10)) /Users/kchan/iota.txt unix-user /Users/kchan/> iota.txt ("0" "1" "2" "3" "4" "5" "6" "7" "8" "9")
In the above example, if iota.txt
does not exist and I use setq
instead of defile
, an internal symbol named IOTA.TXT
will be created in UNIX-USER
package instead and I will write to its value cell, rather than /Users/kchan/iota.txt
on the file system.
Command and Process Mapping
Unix in Lisp manages jobs in the unit of Effective processes. Theses include regular Unix processes represented by simple-process
, and pipeline
’s which are consisted of any number of UNIX processes and Lisp function stages.
Simple commands
When Unix in Lisp maps a directory, files are checked for execution permission and executable ones are mapped as Common Lisp macros. These macros implicitly quasiquotes their arguments. The arguments are converted to strings using literal-to-string
, then passed to the corresponding executable.
Examples of using macros mapped from Unix commands
/Users/kchan/some-documents> (cat ,@(ls)) ;; This cats together all files under current directory.
You can also set up redirections (and maybe other process creation settings in the future) via supplying keyword arguments. These arguments are not implicitly quasiquoted and are evaluated.
/Users/kchan/some-documents> (ls :output *terminal-io*) ;; This outputs to *terminal-io*, which usually goes into *inferior-lisp* buffer.
/Users/kchan/some-documents> (ls :error :output) ;; This redirect stderr of ls command to its stdout, like 2>&1 in posix shell
Like you have discovered in (cat ,@(ls))
, effective processes can be used like Lisp sequences – they designate the sequence of their output lines.
Pipeline
Pipelines are created via the pipe
macro:
/Users/kchan/quicklisp/local-projects/unix-in-lisp> (pipe (wc -l) (ls)) 9
Under the hood, except the first stage, each stage of the pipeline is passed :input
as an additional argument. Alternatively, if there are arguments _
, they are substituted with the result of the previous stage. You can mix Lisp functions and values with Unix commands. Using Lisp value as the first input stage is easy enough:
/Users/kchan> (pipe (iota 10) (wc)) 10 10 20
The _
extension make it easy to add Lisp functions to the mix:
/Users/kchan> (pipe (ls) (filter (lambda (s) (> (length s) 10)) _) (wc -l)) 47
The above counts the number of file with filename longer than 10 under my home directory.
Interactive Use
Inside a unix-in-slime
listener, if the primary value of evaluation is an effective process and it has avaliable input/output streams, unix-in-slime
automatically “connect” it to the listener, i.e. I/O of the listener is redirected to the process, similar to foreground processes in POSIX shell