Skip to content
AgileLogics
6 min read

Files and I/O

Prerequisites: Error handling, Java interop

spit and slurp

(spit "notes.txt" "first line\n")              ; write (replaces)
(spit "notes.txt" "second line\n" :append true)
(print (slurp "notes.txt"))
(println (count (clojure.string/split-lines (slurp "notes.txt"))))
first line
second line
2

spit writes a string to a file and slurp reads a whole file (or URL) into a string — perfect for small files. Both use UTF-8 by default.

Reading large files line by line

(require '[clojure.java.io :as io]
         '[clojure.string :as str])

(spit "scores.csv" "name,score\nada,91\nlinus,78\ngrace,88\n")

(with-open [rdr (io/reader "scores.csv")]
  (let [rows (->> (line-seq rdr)
                  rest                                  ; skip header
                  (map #(str/split % #","))
                  (map (fn [[name score]] {:name name :score (parse-long score)}))
                  doall)]                               ; realize before closing!
    (doseq [{:keys [name score]} rows]
      (println (format "%-6s %s" name (if (>= score 80) "pass+" "pass"))))
    (println "best:" (:name (apply max-key :score rows)))))
ada    pass+
linus  pass
grace  pass+
best: ada
  • with-open opens resources and closes them when the block ends, even after an exception.
  • line-seq returns a lazy sequence of lines. Consume it (or doall it) inside with-open — reading after the file is closed throws an error.

Writing with a writer

(require '[clojure.java.io :as io])

(with-open [w (io/writer "report.txt")]
  (.write w "Report\n")
  (doseq [i (range 1 4)]
    (.write w (str "item " i "\n"))))
(print (slurp "report.txt"))

(binding [*out* (io/writer "report.txt")]
  (println "printed straight into the file")
  (flush))
(print (slurp "report.txt"))
Report
item 1
item 2
item 3
printed straight into the file

binding temporarily redirects *out*, so println writes to the file.

Files and directories

(require '[clojure.java.io :as io])

(io/make-parents "project/src/app.clj")        ; create missing folders
(spit "project/src/app.clj" "(println :app)")
(spit "project/README.md" "# Project")

(let [root (io/file "project")]
  (println (sort (for [f (file-seq root) :when (.isFile f)]
                   (.replace (str (.relativize (.toPath root) (.toPath f))) "\\" "/"))))
  (println (.exists (io/file "project/README.md")))
  (doseq [f (reverse (file-seq root))] (io/delete-file f))
  (println (.exists root)))
(README.md src/app.clj)
true
false
  • io/file builds java.io.File objects; file-seq walks a directory tree.
  • Deleting in reverse order removes files before their folders.
  • io/resource finds files on the classpath (like resources/config.edn) — the usual way to ship data files with an application.