Recently I was working on a simple Clojure program where I need to read input from STDIN. I hadn't actually done this before, so I searched online and found others had similar questions, but had to cobble an answer together due to two issues:
In most languages you use a while loop, check for some condition (or input) to be false in order to stop. Clojure actually has a while loop construct, but making it work seems tricky and likely involves mutable state. Paying the overhead of STM (Software Transactional Memory) in order to do a simple input loop, seems like excessive overkill.
When you try to read from STDIN using
lein run
, it doesn't work. The input is ignored.
So, here's what I have working that is reasonably idiomatic and concise.
In my code example, I want to read a user's input until they type in a sentinel value, which I chose to be :done
. Here I just echo the output back:
(ns example.stdin) | |
(defn -main | |
"Read from STDIN" | |
[& args] | |
(println "Enter text:") | |
(loop [input (read-line)] | |
(when-not (= ":done" input) | |
(println (str "You entered: >>" input "<<")) | |
(recur (read-line)))) | |
(println "End")) |
The trick to getting it work with with Leiningen (I'm using lein-2.0.0-preview10) is to use lein trampoline run
, rather than lein run
. The trampoline feature allows you to run your app in a separate JVM process rather than within the Leiningen process (as a child process). Running it as a child process somehow blocks STDIN input from being captured.
Example usage:
$ lein trampoline run -m example.stdin
Enter text:
First line
You entered: >>First line<<
Second line
You entered: >>Second line<<
Foo
You entered: >>Foo<<
:done
End
In all likelihood, you would want to capture and retain the input lines in a collection. To do that use an accumulator in your loop:
(ns example.stdin) | |
(defn do-something-cool [v] | |
(println v)) | |
(defn -main | |
"Read from STDIN" | |
[& args] | |
(println "Enter text:") | |
(loop [input (read-line) acc []] | |
(if (= ":done" input) | |
(do-something-cool acc) | |
(recur (read-line) (conj acc input)))) | |
(println "End")) |
Ideas on more idiomatic ways to do this in Clojure are welcome.