Closh – Bash-like shell based on Clojure
github.com
github.com
I'm currently waiting on Elvish [1] to mature. It's written in Go [2], but they're developing a new language on top of it for shell scripting, which I tried for a week. It's a nice modernization and is incredibly fast but still very alpha.
You don't realize how slow ZSH/Bash is (even without plugins) until you try a modern shell, and there are very few good modern shells around.
Besides performance my primary complaint is just how awful coding in ZSH is. I've been doing it for years and I still feel like I'm running into walls constantly and struggling to write 'clean' code that isn't full of hacks and workarounds.
High performance + modern scripting language = my ideal future shell
I've found the complete opposite to be the case. This is one area where there are hundreds of different shells out there as it seems a "cool" thing to try since it's basically just an extension of writing your own language. Most never go beyond pet projects though.
If you're curious about Go shells, I'm writing one too[1]. It's also alpha but you can already write some reasonably complex scripts in it since it supports message passing, network sockets and background processes. It's definitely not fast though. Fast enough for what I need it to do but there's plenty of room for performance tuning once in beta.
I have had the same experience. For every scripting/interpreted language out there, there is at least one person who thinks "ooh, wouldn't it be neat if I could use this as a shell?" or, almost as often, "to program my text editor?" Soon they realize that interactive programs like shells and text editors are hard to get right. There are thousands of special cases and non-obvious features that have only been found by hard experience. These programs must also interact with a computing world that has evolved over decades.
Most programmers give up at this point. A few persist, and of those, a few end up creating something genuinely useful to more than themselves and a couple of friends. I'm not sure if I would call shells a "solved" problem, but I don't believe the incredible amount of work required to make a sufficiently better one is worth it.
CLASH - Common Lisp Again SHell? Oh. http://www.cliki.net/CLISP-Shell
Doh! https://docs.racket-lang.org/rash/index.html
Someone put OO in my Linux shell! https://virtualizationreview.com/articles/2017/06/01/how-to-...
[0]: https://github.com/rakitzis/rc
Secondly, I use rc in emacs shell-mode which poorly emulates a terminal, so advanced completion and all that other stuff I delegate to emacs. rc is just a programming language that supports shell commands extremely well.
I think xonsh is a good modern shell to replace bash/zsh/*sh. The only two issues are: 1) it has the stupidest name ever with an even more stupid pronunciation, 2) it's great, it's going toward a great direction, but it fails in one of your key areas for what makes a good shell: speed, it's very slow.
But regarding speed, please tell me how you find most shells to be too slow? Most of the time you're in shell you're not even doing shell stuff, you are rather using tools like ls and find and grep, etc. For the almost 2 decades I've been using bash, it never felt "slow" to me. xonsh feels slow, but at least the python-esque of it makes it a pleasure to use (except where you hit areas where it doesn't work well... I hope that improves over time though)
Some of the great features you won't find in Bash or Zsh are the string and array methods[2], first class arrays[3], slicing syntax[4], tuple assignments[5], optionally type-checked assignments[6], ability to use functions within pipelines, typed function parameters, a much simpler syntax, and more. Many of the best ideas from Fish, Oil, Elvish, Bash, Zsh and other shells have been implemented or are in the process of being implemented.
- [1] https://github.com/redox-os/ion/
- [2] https://doc.redox-os.org/ion-manual/ch05-05-method.html
- [3] https://doc.redox-os.org/ion-manual/ch04-02-arrays.html
- [4] https://doc.redox-os.org/ion-manual/ch06-00-slicing.html
- [5] https://doc.redox-os.org/ion-manual/ch04-00-variables.html#M...
- [6] https://doc.redox-os.org/ion-manual/ch04-00-variables.html#T...
If you're looking to contribute to something, the devs are really helpful and open to stuff (I was able to hack together an autopair feature pretty quickly and they took it)
This needs freakin' Node.js.
I'm more looking in the Rust/Haskell/OCaml direction for a hip bash/zsh replacement.
I have to say I have been using BASH, SED and AWK less. They are great tools but in the end it is getting easier and easier to get things done with a Macro in Racket for me.
Yeah, that's an old sentiment. I used to echo the same thing, but it's worth coming to terms with the fact that node is a pretty good platform to build on.
There are zero things I want my shell to do which depend on as much tooling as Node provides.
I want a compiled native binary with as few dependencies as possible for the shell I'm going to use in single-user mode with very few resources available.
* compile to native binaries
* many ways to do concurrency (instead of async all the way)
* a serious language (not created-in-10-days JS)
Node is really step back for me, coming from Ruby and the JVM.
It has improved, sure. But not only in the direction that makes me happy. But you cannot undo some bad design choices, without it becoming a different language.
> I would highly suggest [...]
I write JS at times. It hurts badly. Not everyone feels it. Maybe coming from PHP or Perl it does not even hurt that much. But after writing some Haskell and Elm lately, the pain got more intense.
> Asyncs simplicity is arguably a reason why it’s a good platform to build off of.
Not having choices is never a good thing in my book. And I do not think that JS does async particularly well. So much line noise when it comes to working with promises.
No fan here.
I don't know about PHP (my experience with it is limited), but coming from Perl, Perl is a breath of fresh air in comparison.
Then again, to me Haskell makes even the worst JAPH-style Perl one-liners look readable in comparison, so maybe I'm not the best judge of language design.
Though core.async makes async programming quite nice
Sure you can paint your cage gold, and it will look nicer. But it remains a cage. I want to choose, especially when it comes to how I do my concurrency.
One of the things that Shells have gotten right is postfix function composition (the | operator). This allows you to very easily compose functions to perform complex operations, and this isn't really present in scripting languages. Haskell let's you do this with the `&` operator, and you can write a procedure for Scheme that performs similarly. I'm not sure how this would work with Python or JS.
I'm hoping that in the next few years we'll see a larger break from shell syntax. The biggest thing I think holding this back is REPLs. Most scripting languages would do fine as a shell replacement with the addition of a stdlib for the purpose, but they need support for navigating the file system. Maybe tab-completion for paths like Bash does would be best, but I'm still thinking about it.
.method().method()?
It's also nowhere near as flexible, because it requires the return value to be responsible for providing a set of APIs for the next method call on every step of the chain, whereas each function in a functional composition can operate on any arbitrary input/output.
There's actually a proposal for a pipeline operator in JS that I personally really like: https://github.com/tc39/proposal-pipeline-operator
I have no idea if it's getting enough traction to get standardized anytime soon though.
In lieu of that, I personally just use Ramda's `pipe` function: http://ramdajs.com/docs/#pipe
Which accomplishes the same thing, but with a bit more ceremony, and not quite as convenient in a scripting setting.
And the truth is that functionB(functionA()) reads much worse than functionA | functionB , so yeah I agree that this is something that shell scripting languages got right.
It's not really perfect parity with what a shell typically offers, though, since a shell's pipeline steps (usually) run in parallel and indefinitely, consuming an input stream and producing an output stream (whether those streams are text, rich objects, audio/video data, or something else entirely). Elixir/Erlang are well-suited to achieving that parity by spinning up processes and using message passing for I/O, but Elixir's pipeline operator doesn't really do that (last I checked).
(-> function
function
function)
An interesting take on that is a library called swiss arrows that lets you do (-<> (function arg1 <>)
(function <> arg2)
(function arg1 arg2 <>)
Using the <> to indicate where the result of calling one shall be threaded into the next. (as-> value <>
(function1 arg1 <>)
(function <> arg2)
(function arg1 arg2 <>)) (define (pipe . exprs)
(if (null? (cdr exprs))
(car exprs)
(apply pipe (cons ((cadr expr) (car expr))
(cddr expr)))))
(pipe '(3 4 5) (lambda (x) (map 1+ x))) ;; '(4 5 6) def pipe(init_val, *funcs):
"""Apply `init_val` to the composition of `funcs`
pipe(init, func1, func2, ..., funcn) -> funcn(...(func2(func1(init_val))))
"""
return functools.reduce(lambda val, func: func(val), funcs, init_val)
Check out clojure's threading macros[1] which extends that to taking forms, where the current value is spliced in as the first or last argument. e.g., with ->> (thread-last): (->> '(3 4 5) (map 1+) (reduce +)) ;; 15
Fits the bill for short functions, and you can still have lambdas for when you really need them.Wow that's pretty awesome. I'd been annoyed with needing to use lambdas for this type of thing. I especially like the as-> macro.
I need to take the time to explore Scheme macros to see what other useful abstractions they can provide.
I would appreciate any feedback. What are the less known features of existing shells you really like? What things would you change about existing shells if you could?
I'd suggest swapping stage 3 and stage 2 -- environment vars and signal and job control are more important (IMO) than everything in stage 2, and may affect the syntax more as well.
One other thing that Clojure has, however, which may be a problem here, is a strong emphasis on precise namespacing. That seems a like a serious problem for day to day use of this kind of shell. Pedantically namespacing all the interesting library functions I want to use would get old fast.
For example in eshell you can do:
cd /ssh:some-server:/
top
cd /docker:container:/
find-file etc/hostsAbility to browse documentation of Clojure functions at the shell might help. Currently, man pages can be accessed for utilities. But quickly browsing doc of, say, Clojure re-find will add value, although features from stage 2 and 3 of roadmap are more important/show-stoppers.
Also, what's the current status on autocompletion features?
By the way, very nice work!
Check out the example: https://github.com/dundalek/closh/blob/master/doc/guide.md#c...
This looks great, though, and I can't wait to try it out.
(let [x #sh ls] (pprint x))
Or some kind of way to embed shell execution directly in the form...
why? seriously, is this the point that modern tooling has gotten to where for no apparent reason it has a node dependency?
And I expect the right implementation for many of these params will be running separate daemons anyway (eg. you clearly want one per user).
But many parts of code are platform agnostic. I think it would be cool to eventually port the system specific parts to java apis and have both.
(require 'cider)
(setq cider-cljs-lein-repl
"(do
(require 'figwheel-sidecar.repl-api)
(figwheel-sidecar.repl-api/start-figwheel!)
(figwheel-sidecar.repl-api/cljs-repl))")
Then I just use figwheel to manage my cljs projects (I think this is the default now for reagent and luminus projects, it's also the default for re-natal for react native development in cljs). Life is pretty good and I have a nice debugger via Chrome.I prefer the debugger in CIDER (which doesn't work for cljs) to the debugger in Chrome, but the Chrome debugger is decent (and it's what I'd have in Javascript anyway). There is a nice chrome extension to help with clojurescript eval in the browser/debugger called dirac: https://github.com/binaryage/dirac
Theres no meaningful reason to use cljs other than to avoid java or if the java runtime isn’t available (eg. browser).
lumo/cljs is instantaneous
The downsides are pretty darn significant though. I can’t imagine a situation where I would trade ‘starts a bit faster’ for ‘throw away all parallelism in code’ personally... but hey. Plenty of people use node for very serious stuff.
I suppose I just think this is an example of fast and cheap on the fast/cheap/good scale.
One can even AOT to native code, if desired.
Its slow because a bare bones hello world takes 40ms in Java instead of 1ms in C. Its "slow" relative to other languages, but its not really that slow over all. I can use mvn without noticing that its slow, but leiningen takes multiple seconds to run a command.
Take a look at this image, taken from the link pjmlp posted: http://blog.ndk.io/img/jvm_vs_clojure.svg
Note that the Clojure team are working on improving this, but it will never be as fast as pure Java and certainly never as fast as other languages (including cljs on node)
Second, it is a well known fact in the Clojure community since several years, that the blame lies in Clojure itself.
http://blog.ndk.io/jvm-slow-startup.html
https://dev.clojure.org/display/design/Improving+Clojure+Sta...
> ...
> It treats everything as text while we mostly need to manipulate structured information.
Looking at the examples given, it's more like Powershell, but with better compatibility with existing Unix text-based tools. Which is awesome.
It would need to be based on Illumos and not Linux, though... IIRC, the only existing open-source filesystem that supports anything akin to a resource fork is ZFS, and the Linux kernel doesn't have the syscalls readily access it (ZFS implements extended attributes as forks, but the kernel has really tight limitations on what can be done with xattrs, much tighter than what ZFS can do).
It's just a fantasy, though, and not something I have the skills or the time to do.
Which kind of makes Windows, with VS, .NET and Powershell, the closest to those environments.
As alternative, if one is feeling bored, why not create an FPGA for it? :)
[0] http://xon.sh
node uses pthreads for certain asynchronous operations like stat(). How does Closh handle forking when there are multiple threads?
I am curious, what does fish use threads for and how does it handle them?
eshell[0] is another one. Has a few warts, but is generally pretty cool.
Someday I'd like to write one in Common Lisp. Just haven't gotten a round tuit yet.
[0] https://www.gnu.org/software/emacs/manual/html_mono/eshell.h...
(run/string (| (ps aux) (grep foo) (grep -v grep))
is quite a bit more of a pain than: ps aux | grep foo | grep -v grep
Or at least I think so.It's really meant for providing a nice Lispy interface to POSIX, and at that I believe it succeeds.
(run sh)
again.. haven't ever used as login shell though :)Good overview of its power here: https://jakevdp.github.io/PythonDataScienceHandbook/01.00-ip...