This is a read-only archive!

Clojure: redirecting output to a file

In a scripting language, you often run scripts from a command line and print things to STDOUT. For long output you can redirect it to a file via shell-redirection ($ foo.rb > foo.txt).

But if you're using Clojure for ad-hoc scripting, you're probably sitting in a REPL, not running from a command line. REPLs generally don't have shell-redirection. Printed output just gets dumped into your REPL. This can be annoying. (After a good 10,000 lines of output into a REPL buffer, Emacs starts to lag.)

But you can write a macro to handle this easily enough.

(use 'clojure.contrib.duck-streams)
(defmacro redir [filename & body]
  `(spit ~filename (with-out-str ~@body)))

Then:

user> (redir "foo.txt" (foo) (bar) (baz))

Now whatever (foo) and friends would've printed to STDOUT will instead go to foo.txt. I've found this somewhat useful at work lately.

Edit: better version via Graham Fawcett:

(defmacro redir [filename & body]
  `(binding [*out* (writer ~filename)] ~@body))
November 12, 2009 @ 1:23 PM PST
Cateogory: Programming
Tags: Clojure

6 Comments

Dan Ballard
Quoth Dan Ballard on November 12, 2009 @ 2:30 PM PST

Ha, neat trick. Thanks. I'm sure this will work well enough in any Lisp dialect language :)

Brian
Quoth Brian on November 12, 2009 @ 2:41 PM PST

Yeah I'm pretty sure Common Lisp can do it. Any language that lets you dynamically bind the STDOUT handle to some sort of string-writer will do it. You could probably even do it in Ruby with blocks.

Graham Fawcett
Quoth Graham Fawcett on November 13, 2009 @ 4:57 AM PST

Correct me if I'm wrong, but doesn't your example buffer all of the output of the body (into a string), and then push the string out to a file?

Why not just dynamically bind *out* to the opened file?

Brian
Quoth Brian on November 13, 2009 @ 5:22 AM PST

Right, good point.

Tom Crayford
Quoth Tom Crayford on January 02, 2010 @ 1:40 AM PST
(doc with-out-str)
-------------------------
clojure.core/with-out-str
([& body])
Macro
  Evaluates exprs in a context in which *out* is bound to a fresh
  StringWriter.  Returns the string created by any nested printing
  calls.
nil
Jörg Ramb
Quoth Jörg Ramb on January 12, 2012 @ 11:55 PM PST

You should make sure that the output stream is flushed and closed. The later version does not do this, I suggest this instead:

(defmacro redir
  [filename & body]
  `(with-open [w# (writer ~filename)]
     (binding [*out* w#] ~@body)))