For over a month now I’ve been experimenting with a totally new concept to me: functional programming. I’m not going to go deeper into what it is (that’s for another blogpost) so sorry for those who don’t know it yet.
The language I used to take these first steps is ‘Clojure’ (a language with a lisp-like syntax) which I really start loving. In fact I like it so much that I want to be able to control my unix machine with a similar syntax. And this is exactly what my goal is with this new project. Clojure syntax for using the linux terminal!

The idea

What is it exactly what I want to achieve?

First of all,
I want to be able to use commands like

  • ls -a | sort
  • cat * | grep “Linux” | grep -v “UNIX” | wc -l

into a lisp-like syntax

  • (sort (ls -a))
  • (wc -l (grep -v “UNIX” (grep “Linux” (cat *))))

which I believe to be an improvement to the pipes. Mostly because of the readability.

So this is like converting nested lists to pipes. For this to work the nested lists must always appear the end of the parent list.
At this point it is no more than a syntax conversion.

So secondly,
I want to do better. I want to be able to put nested lists (nested commands) at any place. What would this look like?

(cmd1 –arg1 (cmd2 … ) –arg2 (cmd3 ….))

I’m still thinking of examples of where this would be useful (dd?). If you can let me know (and everyone) know in the comments below!

The real stuff
As an exercise for myself I challenged myself to create a syntax converter in clojure.

So here is a first tryout:

(def sample-string "(sort (ls -a))")

(defn pipe
 ([a] (clojure.string/join " " a))
 ([a b] (str (pipe a) " | " (pipe b))))

(defn stack-to-linux [stack]
 (reduce pipe stack ))

(defn split-from-subcommand [l] (split-with #(not (coll? %)) l))

(defn valid-cmd? [cmd] (not (or (empty? cmd))))

(defn list-to-stack [l stack]
 (let [[first* r] l]
   (if (coll? first*)
       (list-to-stack first* stack)
       (dosync
         (let [[cmd1 cmd2] (split-from-subcommand l)]
           (alter stack conj cmd1)
           (when (valid-cmd? cmd2)
             (list-to-stack (first cmd2) stack)))))))

(defn string-to-stack [string]
  (let [stack (ref '())]
    (list-to-stack (read-string string) stack)
    @stack))

(defn funix [string] (stack-to-linux (string-to-stack string)))

I’m using a feature of clojure: clojure code is clojure data. Its really really fancy (and helpful)

What currently works:
(funix  “(sort (ls -la)” ) –> “ls -la | sort”

I still have problems with strings in the string:

(funix "(grep "a" (ls -la))")

ArityException Wrong number of args (3) passed to: user$lilisp  clojure.lang.AFn.throwArity (AFn.java:437)

#So I tried escaping it
(funix "(grep \"test\" (ls -la))")

#returns (la -la | grep test)  which is not what I want of course (the quotes are missing)

If you have ideas on how to improve this let us know in the comments. Also join me at https://code.google.com/p/funix/
Also if you think my idea is just stupid (or great) let me know as well 🙂

Share and enjoy!

8 thoughts on “fUnix: Enhancing the linux terminal by thinking functional

  1. Maybe you should print the result of (funix “(grep \”test\” (ls -la))”) with prn, as suggested by xeqi

    1. Thank you for telling me this – I didn’t come across this article yet. Do you have any idea why this great idea hasn’t become popular? I’m really interested in how people reacted on your article! Its kinda funny that I think of it now

  2. Nice idea, but one simple question : Why would you choose for Clojure = java with Clojure running on top, over : Haskell, one single install needed ?

    1. Because.
      1) I’m exercising clojure
      2) Clojure will provide a very fast way to prototype because: “clojure code is clojure data” and the syntax I want to use is in fact clojure syntax
      3) clojure is cool!

  3. cat | grep? Tsk tsk.

    grep “Linux” * | grep -v “UNIX” | wc -l

    …which would translate to:

    (wc -l (grep -v “UNIX” (grep “Linux” *)))

  4. For pipeline results as arguments, typical shell scripts use backquote to turn the stdout into a string value and that is certainly something you can support. Backquote is already reserved in Clojure so a pleasant syntax might be tricky and I don’t know enough Clojure to suggest one. Hmmm, perhaps since backquote looks like it evaluates the form then perhaps it could just as you need since the result would be a pipeline or process object. If you encounter one of those as an argument then stringify it.

    (cmd1 –arg1 `(cmd2 … ) –arg2 `(cmd3 ….))
    ==>
    (cmd1 –arg1 (result of evaluating cmd2) –arg2 (result of evaluating cmd3))

Leave a reply to Chris Cancel reply