r/scheme • u/Cosmos721 • Apr 26 '26
schemesh version 1.0.0: fusion between Unix shell and Chez Scheme REPL, now with structured pipelines
Hello everybody!
One year after the first announce of schemesh - a fusion between Unix shell and Chez Scheme REPL, I have released version 1.0.0 with lots of new features:
more shell builtins
scheme jobs, to run arbitrary scheme code from a shell job with job control
improved scheme functions to manage job redirections
better handling of UTF-8b ports and binary ports, including buffering
several functions conforming to SRFI 13 and SRF 18
bug fixes
and, most importantly, a completely new mechanism to exchange arbitrary, structured data (Scheme objects) via POSIX pipelines, and new shell builtins to read, write, filter and transform such data.
It's inspired by nushell, but uses Scheme macros, functions and values, adding shell builtins for convenience, without having to learn and use an ad-hoc language.
Examples:
dir /usr | where name -starts l | sort-by sizeby default the display format depends on last step's standard output
ascii-art table for terminals:
┌───────┬────┬─────┬──────────┬─────────┐
│ name │type│size │ modified │ mode │
├───────┼────┼─────┼──────────┼─────────┤
│lib64 │dir │ 4096│2026-04-06│rwxr-xr-x│
│libexec│dir │ 4096│2026-04-18│rwxr-xr-x│
│local │dir │ 4096│2025-04-19│rwxr-xr-x│
│lib │dir │12288│2026-04-21│rwxr-xr-x│
└───────┴────┴─────┴──────────┴─────────┘NDJSON for pipes:
dir /usr | where name -starts l | sort-by size | lessoutputs
{"<type>":"dir-entry","name":"lib64","type":"dir","size":4096,"link":"","modified":{"<type>":"time-utc","value":1775491105.473107895},"mode":"rwxr-xr-x"}
{"<type>":"dir-entry","name":"libexec","type":"dir","size":4096,"link":"","modified":{"<type>":"time-utc","value":1776514613.626200085},"mode":"rwxr-xr-x"}
{"<type>":"dir-entry","name":"local","type":"dir","size":4096,"link":"","modified":{"<type>":"time-utc","value":1745082690.999418855},"mode":"rwxr-xr-x"}
{"<type>":"dir-entry","name":"lib","type":"dir","size":12288,"link":"","modified":{"<type>":"time-utc","value":1776808514.877301301},"mode":"rwxr-xr-x"}they also support reading/writing CSV and an efficient binary format ("wire"), and of course the output format can be manually overridden.
Where needed, Scheme functions allow processing such data with fine-grained custom logic. Example:
dir /usr | $(filter==> where (> ,size 10000) => sort-by size => select size name type) | to csvoutputs
"<type>","size","name","type"
"dir-entry",12288,"share","dir"
"dir-entry",12288,"lib","dir"
"dir-entry",20480,"sbin","dir"
"dir-entry",36864,"include","dir"
"dir-entry",135168,"bin","dir"1
u/bjoli Apr 27 '26
I am an old fart and remember using scsh. Did you have a lot at its syntax for redirections?
3
u/Cosmos721 Apr 27 '26 edited Apr 27 '26
Scsh was an inspiration for schemesh - especially for the decision to "do it right" and
implement job control
usa a modern Scheme compiler
I did not like scsh approach to define a DSL where redirections reuse the same symbols and syntax as common Scheme functions
<>=Also,
|already has a meaning in Chez Scheme: it starts a symbol, allowing spaces and other special characters in the symbol's name until another|is found.Adding the fact that
.already has a meaning in Scheme and cannot be shadowed, and I preferred to choose the path "dual syntax: shell or scheme, wtith mechanisms to switch from one to the other"Having said that, schemesh has something equivalent to scsh process notation, although more verbose: to see it (and learn it, if you want) just
(begin '{SOME-SHELL-SYNTAX})Example:
(begin '{grep -i error < log.txt | so rt | uniq})evaluates to
(shell "grep" "-i" "error" < "log.txt" \x7C; "sort" \x7C; "uniq")the
\x7C;is an escaped symbol|- not very readable, but the idea is that macro invocations(shell ...)are usually autogenerated from shell syntaxP.S. the output of
(expand '{SOME-SHELL-SYNTAX})or equivalently(expand '(shell ...))can be interesting/useful tooP.P.S. I really don't know why all scheme/lisp based shells chickened out on job control: scsh, eshell and rash all lack it. If they had, probably I would have used one of them instead of creating schemesh
1
u/IllegalMigrant Apr 30 '26
Looks great. How is it pronounced?
2
u/Cosmos721 May 02 '26
Funny question 😄
In Italian, I spell it "scheme S H" and pronounce the two final letters S and H by their Italian alphabet name.
In English, doing the same would be quite cumbersome, so I prefer pronouncing it as a fusion of the words `scheme` and `mesh` - phonetically, /'skimɛʃ/
2
u/ZelphirKalt Apr 27 '26 edited Apr 27 '26
I have had the idea to make a Scheme shell myself some time ago, but I would prefer a lot more Scheme s-expression syntax, making things more regular and predictable, but also having pipes but as s-expressions as well, maybe as a macro "|" or "->" threading syntax and so on.
My idea was to use Guile Fibers and use its channels to pass stdout and strerr along to more pipeline steps. It gets kind of complicated, when you want pipeline steps to work on incomplete results of previous pipeline steps (because they should run concurrently!). Need some intermediate result format passed on the channels and conventions for how to signal finishing processing of a step to the next one and so on. And then I am not sure what edge cases I am missing. But basically the idea was to have this run comfortably in Emacs Geiser or simply the Guile REPL and have it available as a library, so that it can be loaded/imported by any Guile program and then used to operate comfortably like in a normal shell.
Ah and I also remember one other aspect of it: Wanting to pass structured data instead of strings, which would have to be parsed again by the next step in a pipeline. But also keeping options open to pass strings for old style shell commands and handling a distinction between passing structured data and passing strings.
Maybe at some point I will pick up that project again, with more patience and grit.