You might have heard of sh, which brings python an interface to call subprocesses. The API of sh is pretty cool: Every command can be treated as a python function, and imported from a namespace. Options and arguments are passed in as python string.
But I think in Clojure, things can be even cooler. We dynamically create symbols for every program. We will have a beautiful DSL so you don’t have to quote arguments as string. So when you are using this library, it may look like:
(uname -a)
(ip -4 addr)
And actually it’s just like that! I create this library called shake. When you load `shake.core`, it indexes all the executables in your path. Then all programs are available to you in a clojure native way.
(uname -a) ;; returns a java.lang.Process, that you can send data, read data and wait for termination.
;; for those just need output
(alter-var-root *print-outpt* (fn [_] true))
(uname -a)
;; it prints ...
There’s a lot of fun in implementing this library. First, to be able to use custom symbol in the DSL, you have to make these executables as macros. Second, find a way to programmably create vars which are named by string. The power of Clojure enables all the ideas and makes it possible. Check out the source code if you are interested in: https://github.com/sunng87/shake/.
The fact that they’re macros instead of functions might make passing arguments slightly more convenient, but it makes it difficult to do things like mapping them onto sequences, composing them, etc. etc. Macros have some severe disadvantages, and to ignore these disadvantages for the sake of a small convenience seems like it might be a bad idea.
Pretty cool. Have a look at what Hugo did in Pallet, too: I’ve used that quite successfully to do something very similar.
I’m just waiting for someone to write (xargs -0 …) and get surprised.
Just to be a little more explicit:
(xargs -0) will be read as (xargs 0), which means that instead of getting the -0 option, the program will receive “0″ as a non-option arg. This is a bug waiting to happen.
http://hugoduncan.org/post/2010/shell_scripting_in_clojure_with_pallet.xhtml
This is very cool, nice work! I’m a bit scared by a couple of things…
“When you load `shake.core`, it indexes all the executables in your path.”
- How long does that take? Overtone and Quil have these side-effecty load behaviours, which I find makes it hard to use them non-interactively (i.e. as a library in another app). I might have a huge app that compiles in milliseconds, but for this one file load :-/
I’m pretty sure “sh” in Python has a lazy way of doing this? You have to either use “sh.cmd()” or “from sh import cmd” to explicitly access a function before it exists, IIUC.
Also, it sounds like this library isn’t being a good citizen in terms of namespaces? I would bet that the odds of a collision with something on my path are high if I “use” into an existing namespace in some project? If not on mine, then what if I’m unlucky enough to send some code to a colleague with a totally different environment and set of commands on his path? Sure, I could require and alias but then I’d have to prefix those nice, convenient symbols; “(sh/ls -hl)”?
How about just having a top-level “sh” macro, where I can call commands as functions within its scope? For example:
(sh
(ls -hl))
I think that would get us out of namespace offences, as the macro could inspect the forms passed to it and instead return a let binding for them? You could also just try to resolve things I actually try to call in the path, instead of loading everything when I use the library?
Just opinions! This looks awesome anyway, can’t wait to give it a go, thanks for making it!
I think the top-level macro is a good idea. But it will have little difference with clojure.java.shell/sh . It looks like pallet is doing shell scripting in that way (as someone mentioned above): http://hugoduncan.org/post/2010/shell_scripting_in_clojure_with_pallet.xhtml
It’s definitely better to have executables lazily loaded. And I will try to figure out if there is another way to do that.
the “$” syntax does not seem to work? I even tried the syntax in the readme file. Can you suggest what I could be doing wrong?
Thanks,
Sunil.
Are you using the latest release ?
And could you please paste me the exception you encountered?
Thank you.