Reading Files in Clojure

Reading and writing files are basic tasks needed for many Clojure programs. First we’ll look at some examples of reading files.

(ns reading-files
  (:require [clojure.java.io :as io]))

;; Reading files requires checking most calls for errors.
;; This helper will streamline our error checks below.
(defn check [result]
  (when (instance? Exception result)
    (throw result)))

(defn main []
  ;; Perhaps the most basic file reading task is
  ;; slurping a file's entire contents into memory.
  (let [dat (slurp "/tmp/dat")]
    (print dat))

  ;; You'll often want more control over how and what
  ;; parts of a file are read. For these tasks, start
  ;; by opening a file to obtain a reader.
  (with-open [rdr (io/reader "/tmp/dat")]
    ;; Read some bytes from the beginning of the file.
    ;; Allow up to 5 to be read but also note how many
    ;; actually were read.
    (let [b1 (char-array 5)
          n1 (.read rdr b1)]
      (printf "%d bytes: %s\n" n1 (String. b1 0 n1)))

    ;; You can also seek to a known location in the file
    ;; and read from there. In Clojure, we can use the Java
    ;; RandomAccessFile for this purpose.
    (with-open [raf (java.io.RandomAccessFile. "/tmp/dat" "r")]
      (.seek raf 6)
      (let [b2 (byte-array 2)
            n2 (.read raf b2)]
        (printf "%d bytes @ %d: %s\n" n2 6 (String. b2 0 n2))))

    ;; The io library provides some functions that may
    ;; be helpful for file reading. For example, we can
    ;; use line-seq to read lines from a file.
    (doseq [line (take 2 (line-seq rdr))]
      (println line)))

  ;; There is no built-in rewind, but we can
  ;; simply open the file again to start from the beginning.
  (with-open [rdr (io/reader "/tmp/dat")]
    ;; The clojure.java.io namespace provides a buffered
    ;; reader that may be useful both for its efficiency
    ;; with many small reads and because of the additional
    ;; reading methods it provides.
    (let [b4 (.readLine rdr)]
      (printf "First line: %s\n" b4))))

(main)

To run the program:

$ echo "hello" > /tmp/dat
$ echo "clojure" >>   /tmp/dat
$ clj reading-files.clj
hello
clojure
5 bytes: hello
2 bytes @ 6: cl
hello
clojure
First line: hello

In this Clojure version:

  1. We use slurp to read the entire file contents.
  2. For more controlled reading, we use clojure.java.io/reader.
  3. For seeking to specific positions, we use Java’s RandomAccessFile.
  4. We demonstrate line-by-line reading using line-seq.
  5. The with-open macro is used to ensure proper resource management.

Note that Clojure, being a JVM language, leverages Java’s IO capabilities. This allows for more direct file manipulation when needed, as shown with the RandomAccessFile example.

Next, we’ll look at writing files.