
Closh – Bash-like shell based on Clojure - tosh
https://github.com/dundalek/closh
======
dmix
Nice, I've been waiting for a good modern shell to replace ZSH. Fish just
wasn't enough of an improvement for me to abandon ZSH. This is an area where
most open-source developers have avoided, as it's a "solved" problem, but it
really isn't.

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

[1] [https://elvish.io/](https://elvish.io/)

[2] [https://github.com/elves/elvish](https://github.com/elves/elvish)

~~~
microcolonel
I'm a big fan of fish, particularly with omf. Though honestly it has some
performance issues, particularly if your prompt line calls something that
could take some time to resolve (such as git status or a du).

~~~
masukomi
I've been having big performance issues with tab completion. Also, I just
don't find the language to be that much better than bash, and writing in fish
means i can't share my shell scripts with any coworkers. I've been writing
everything in bash lately even though i still use fish.

~~~
username223
Same. I love fish as an interactive shell, but write all my scripts in Bash or
some other, suitable scripting language. I don't mind having separate
interactive and batch languages.

------
cies
Call me old. But I want my shell to be a tight binary, preferably distributed
(at some point) by major distros.

This needs freakin' Node.js.

I'm more looking in the Rust/Haskell/OCaml direction for a hip bash/zsh
replacement.

~~~
sillysaurus3
_This needs freakin ' Node.js._

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.

~~~
cies
Could not agree less. It's not totally bad, but for sure not good. I prefer:

* 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.

~~~
giantsloth
JavaScript looks nothing like the “created in 10 days” version to which you
speak. I would highly suggest you take an uncomfirmation(?) biased look at the
language as it is now. Asyncs simplicity is arguably a reason why it’s a good
platform to build off of. I agree I would rather have a single binary, and
node may not be the best platform for shells on this point.

~~~
cies
> JavaScript looks nothing like the “created in 10 days” version to which you
> speak.

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.

~~~
yellowapple
"Maybe coming from PHP or Perl"

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.

------
huntie
This is interesting, but I feel that making shell support such a first-class
citizen is a mistake. Shell languages basically treat system binaries as
functions, which causes you to lose a lot of power with everything being a
string. Using system binaries (cat, echo, etc.) isn't really necessary for a
modern command language due to the ubiquity of scripting languages and module
systems. It's much more powerful to write procedures that work with structured
data to perform similar actions.

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.

~~~
aaron-lebo
> I'm not sure how this would work with Python or JS.

.method().method()?

~~~
yoavm
I think the concept of piping is actually more similar to function(function())
than .method().method(), because you're not limited to the scope of your
initial object's methods.

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.

~~~
michaelmrose
Clojure has threading macros that turn (function(function(function) into

    
    
        (-> 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.

~~~
robto
That's in the standard library, actually. It's called as->.

    
    
        (as-> value <>
              (function1 arg1 <>)
              (function <> arg2)
              (function arg1 arg2 <>))

~~~
michaelmrose
Found the old swiss arrows and didn't realize that as-> even existed thank
you.

------
kloud
Author here, thanks for posting. The project is still very early in the
development and there is a lot of work to be done to make it ready for daily
use.

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?

~~~
encloser
I really like this idea and I really like Clojure. Though I'm curious about
performance. And do you know if anyone is doing something similar with Racket?
(Looks like there is a Common Lisp shell [http://www.cliki.net/CLISP-
Shell](http://www.cliki.net/CLISP-Shell))

~~~
lvh
eshell is another lispy shell that's been in Emacs for a very long time. Elisp
is probably my least favorite lisp that's commonly available, but eshell
probably has the largest user base and is certainly the most mature out of all
of these contenders. You can reasonably use eshell as your primary shell.

~~~
anonacct37
I use eshell daily. It's nice to have a shell that also integrates with with
list, but there's also a ton of value-add with integration with the emacs
ecosystem.

For example in eshell you can do:

    
    
        cd /ssh:some-server:/
        top
        cd /docker:container:/
        find-file etc/hosts

------
LesZedCB
> npm install -g lumo-cljs closh

why? seriously, is this the point that modern tooling has gotten to where for
no apparent reason it has a node dependency?

~~~
mbrock
Node is the runtime for ClojureScript. The whole shell runs in Node.

~~~
LesZedCB
what's the tradeoff of clojurescript over native clojure? wouldn't the java
clojure runtime be much faster?

~~~
lvh
Steady-state performance tends to be better on the JVM but startup performance
is always much better on top of JavaScript. That makes it an enticing trade
off for shells which is a process that you start new ones of constantly and
where the performance of the shell itself is often not very important.
(Performance-critical or hard-to-duplicate code like, say, line editing code,
can be done equally well on either platform via FFI.)

~~~
earenndil
Why not just make a server that starts up once in the background and then a
lightweight c frontend to the server that essentially just pipes every
keystroke to the server?

~~~
cben
Shells are _supposed_ to inherit a ton of state from their parent — [e]uid,
groups, working dir, env vars, nice, umask, chroot, ... This takes zero effort
in unix, but would all have to be (securely!) re-implemented to be able to
send code to a shared execution daemon...

And I expect the right implementation for many of these params will be running
separate daemons anyway (eg. you clearly want one per user).

------
nailer
> Why try to reinvent bash?

> ...

> 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.

~~~
pandalicious
It seems like the concepts are orthogonal though. The reason powershell works
is that it replaced the entire shell toolchain with object-oriented
equivalents. Without that you're fundamentally limited because your inputs are
still plain text, so you're still doing the equivalent of awk/sed scripts to
transform text into structured data.

~~~
zokier
That aligns pretty much with my conclusion that to make an useful object
shell, I'd be essentially rewriting the whole userland and probably some
kernel bits too (/proc and /sys etc). So basically building a completely new
OS on top of Linux kernel. I wouldn't expect such project to be very popular..

~~~
amyjess
For quite a while now, I've fantasized about creating an object-oriented toy
OS with an OO shell and something akin to Classic Mac OS's resource forks. As
much information as possible would be stored as OO data structures in
alternate file streams with native tooling to query them.

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.

------
disease
Looking forward to the day I can write backend code in clojure, frontend code
in clojurescript, write all my terminal commands in clojure and use an IDE
that is configurable via clojure code.

~~~
KingMob
Ideally, we want a Symbolics Clojure Machine.

~~~
yellowapple
I suspect that a hardware JVM implementation would get you halfway there.

~~~
pjmlp
There are still a few chipset companies selling CPUs with support for JVM
bytecode.

As alternative, if one is feeling bored, why not create an FPGA for it? :)

------
mindB
Also worth checking out is xonsh [0]. It has a similar goal but with python as
the sh alternative instead of Clojure.

[0] [http://xon.sh](http://xon.sh)

------
justinhj
There is also a Scala repl and scripting tool
[http://ammonite.io/](http://ammonite.io/)

------
ridiculous_fish
One of the tricky things about shells is the interaction between fork and
pthreads. For example, if you fork while another thread holds a lock, you risk
deadlock.

node uses pthreads for certain asynchronous operations like stat(). How does
Closh handle forking when there are multiple threads?

~~~
kloud
JS is single-threaded by design and node encourages use of asynchronous
operations and then runs callbacks on the event loop. Node provides higher-
level non-blocking function spawn, so closh currently does not need to do
forking directly. I hope this can get us far before the need to drop into
lower level and start dealing with threads.

I am curious, what does fish use threads for and how does it handle them?

------
alvatar
Very cool! I use Clojure on a daily basis for coding, this is a great addition
to the arsenal :) Actually, my main argument against _not using it_ is that
Bash has this awkward syntax that is hard to remember if you don't use it
often... But anyway, I keep forgetting it after years so I might as well
embrace it and take the leap to use this. Thanks for the initiative!

~~~
kloud
Thanks, exactly!

------
spot
another lisp shell: [https://scsh.net/](https://scsh.net/)

~~~
zeveb
scsh is awesome, although I don't believe it was intended to be used
interactively.

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...](https://www.gnu.org/software/emacs/manual/html_mono/eshell.html)

~~~
cat199
Scsh runs in a modified scheme48 repl - no problems with interactive use,
though I've not run it as an actual login shell, though perportedly that works

~~~
zeveb
I mean more that it's not ergonomic for interactive use. Typing:

    
    
        (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.

~~~
cat199
Ah sure. But for those cases you can always drop to a normal session with:

    
    
       (run sh)
    

again.. haven't ever used as login shell though :)

------
DigitalJack
This is fantastic. Reminds me of the apple //e days with a shell that was also
a BASIC interpreter.

------
setzer22
I've been meaning to implement something like this for a while, but couldn't
find the time to do it. It is nice to know that it's already done! I'm going
to check it out.

------
raspasov
What a neat idea! Good work!

------
ythn
Is there a bash-like shell based on Python? I would love something like that.

~~~
hssay
ipython shell with its magic commands is pretty powerful, although not a bash
replacement. For example, you can assign output of ls command to a variable
(e.g. a = !ls) and then use that variable as a python list.

Good overview of its power here:
[https://jakevdp.github.io/PythonDataScienceHandbook/01.00-ip...](https://jakevdp.github.io/PythonDataScienceHandbook/01.00-ipython-
beyond-normal-python.html)

