En Clojure 1.3, Cómo leer y escribir un archivo

163

Me gustaría saber la forma "recomendada" de leer y escribir un archivo en clojure 1.3.

  1. Cómo leer todo el archivo
  2. Cómo leer un archivo línea por línea
  3. Cómo escribir un nuevo archivo
  4. Cómo agregar una línea a un archivo existente
jolly-san
fuente
2
Primer resultado de google: lethain.com/reading-file-in-clojure
jcubic
8
Este resultado es de 2009, algunas cosas han cambiado últimamente.
Sergey
11
En efecto. Esta pregunta de StackOverflow ahora es el primer resultado en Google.
mydoghasworms

Respuestas:

273

Suponiendo que solo estamos haciendo archivos de texto aquí y no algunas cosas binarias locas.

Número 1: cómo leer un archivo completo en la memoria.

(slurp "/tmp/test.txt")

No se recomienda cuando es un archivo realmente grande.

Número 2: cómo leer un archivo línea por línea.

(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
  (doseq [line (line-seq rdr)]
    (println line)))

La with-openmacro se encarga de que el lector esté cerrado al final del cuerpo. La función de lector coacciona una cadena (también puede hacer una URL, etc.) en un BufferedReader. line-seqentrega una secuencia perezosa. Exigir el siguiente elemento de la secuencia perezosa resulta en una línea que se lee del lector.

Tenga en cuenta que desde Clojure 1.7 en adelante, también puede usar transductores para leer archivos de texto.

Número 3: cómo escribir en un nuevo archivo.

(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
  (.write wrtr "Line to be written"))

Nuevamente, with-opense encarga de que BufferedWriteresté cerrado al final del cuerpo. Writer coacciona una cadena en un BufferedWriter, que se usa mediante la interoperabilidad java:(.write wrtr "something").

También podría usar spit, lo contrario de slurp:

(spit "/tmp/test.txt" "Line to be written")

Número 4: agregue una línea a un archivo existente.

(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
  (.write wrtr "Line to be appended"))

Igual que el anterior, pero ahora con la opción de agregar.

O de nuevo con spit, lo contrario de slurp:

(spit "/tmp/test.txt" "Line to be written" :append true)

PD: Para ser más explícito sobre el hecho de que estás leyendo y escribiendo en un archivo y no en otra cosa, primero puedes crear un objeto de archivo y luego convertirlo en un BufferedReadero escritor:

(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))

La función de archivo también está en clojure.java.io.

PS2: a veces es útil poder ver cuál es el directorio actual (así que "."). Puede obtener la ruta absoluta de dos maneras:

(System/getProperty "user.dir") 

o

(-> (java.io.File. ".") .getAbsolutePath)
Michiel Borkent
fuente
1
Muchas gracias por tu respuesta detallada. Me alegra conocer la forma recomendada de File IO (archivo de texto) en 1.3. Parece que ha habido algunas bibliotecas sobre File IO (clojure.contrb.io, clojure.contrib.duck-streams y algunos ejemplos directamente usando Java BufferedReader FileInputStream InputStreamReader) que me hicieron más confuso. Además, hay poca información sobre Clojure 1.3, especialmente en japonés (mi idioma natural) Gracias.
jolly-san
Hola jolly-san, tnx por aceptar mi respuesta! Para su información, clojure.contrib.duck-streams ahora está en desuso. Esto posiblemente se suma a la confusión.
Michiel Borkent
Es decir, (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))rinde en IOException Stream closedlugar de una colección de líneas. ¿Qué hacer? Sin embargo, estoy obteniendo buenos resultados con la respuesta de @satyagraha.
0dB
44
Esto tiene que ver con la pereza. Cuando utiliza el resultado de line-seq fuera de with-open, que ocurre cuando imprime su resultado en REPL, el lector ya está cerrado. Una solución es envolver el line-seq dentro de un doall, lo que obliga a la evaluación de inmediato. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent
Para los principiantes reales como yo, tenga en cuenta que los doseqretornos nilpueden conducir a tiempos tristes sin valor de retorno.
Oct
33

Si el archivo cabe en la memoria, puede leerlo y escribirlo con sorbo y escupir:

(def s (slurp "filename.txt"))

(s ahora contiene el contenido de un archivo como una cadena)

(spit "newfile.txt" s)

Esto crea newfile.txt si no sale y escribe el contenido del archivo. Si desea agregar al archivo, puede hacerlo

(spit "filename.txt" s :append true)

Para leer o escribir un archivo en línea, usaría el lector y escritor de Java. Están envueltos en el espacio de nombres clojure.java.io:

(ns file.test
  (:require [clojure.java.io :as io]))

(let [wrtr (io/writer "test.txt")]
  (.write wrtr "hello, world!\n")
  (.close wrtr))

(let [wrtr (io/writer "test.txt" :append true)]
  (.write wrtr "hello again!")
  (.close wrtr))

(let [rdr (io/reader "test.txt")]
  (println (.readLine rdr))
  (println (.readLine rdr)))
; "hello, world!"
; "hello again!"

Tenga en cuenta que la diferencia entre slurp / spit y los ejemplos de lector / escritor es que el archivo permanece abierto (en las declaraciones let) en este último y la lectura y la escritura se almacenan en el búfer, por lo tanto, son más eficientes al leer / escribir repetidamente en un archivo.

Aquí hay más información: slurp spit clojure.java.io Java's BufferedReader Java's Writer

Pablo
fuente
1
Gracias Paul Podría obtener más información mediante sus códigos y sus comentarios, que son claros en el punto que se centra en responder mi pregunta. Muchas gracias.
jolly-san
Gracias por agregar información sobre métodos de nivel ligeramente inferior que no figuran en la respuesta (excelente) de Michiel Borkent sobre los mejores métodos para casos típicos.
Marte
@Mars Gracias. En realidad, respondí esta pregunta primero, pero la respuesta de Michiel tiene más estructura y eso parece ser muy popular.
Paul
Él hace un buen trabajo con los casos habituales, pero usted proporcionó otra información. Por eso es bueno que SE permita múltiples respuestas.
Marte
6

Con respecto a la pregunta 2, a veces se desea que la secuencia de líneas se devuelva como un objeto de primera clase. Para obtener esto como una secuencia perezosa, y todavía tener el archivo cerrado automáticamente en EOF, utilicé esta función:

(use 'clojure.java.io)

(defn read-lines [filename]
  (let [rdr (reader filename)]
    (defn read-next-line []
      (if-let [line (.readLine rdr)]
       (cons line (lazy-seq (read-next-line)))
       (.close rdr)))
    (lazy-seq (read-next-line)))
)

(defn echo-file []
  (doseq [line (read-lines "myfile.txt")]
    (println line)))
satyagraha
fuente
77
No creo que la anidación defnsea ​​un Clojure ideomático. Su read-next-line, por lo que yo entiendo, es visible fuera de su read-linesfunción. Es posible que hayas usado un (let [read-next-line (fn [] ...))en su lugar.
kristianlm
Creo que su respuesta sería un poco mejor si devolviera la función creada (cerrándose sobre el lector abierto) en lugar de ser globalmente vinculante.
Ward,
1

Así es como se lee todo el archivo.

Si el archivo está en el directorio de recursos, puede hacer esto:

(let [file-content-str (slurp (clojure.java.io/resource "public/myfile.txt")])

recuerde requerir / usar clojure.java.io.

joshua
fuente
0
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Dima Fomin
fuente
0

Para leer un archivo línea por línea, ya no necesita recurrir a la interoperabilidad:

(->> "data.csv"
      io/resource
      io/reader
      line-seq
      (drop 1))

Esto supone que su archivo de datos se mantiene en el directorio de recursos y que la primera línea es información de encabezado que se puede descartar.

Chris Murphy
fuente