In Clojure 1.3, Lesen und Schreiben einer Datei

163

Ich möchte die "empfohlene" Art des Lesens und Schreibens einer Datei in Clojure 1.3 kennen.

  1. Wie man die ganze Datei liest
  2. So lesen Sie eine Datei Zeile für Zeile
  3. So schreiben Sie eine neue Datei
  4. So fügen Sie einer vorhandenen Datei eine Zeile hinzu
Jolly-San
quelle
2
Erstes Ergebnis von Google: lethain.com/reading-file-in-clojure
jcubic
8
Dieses Ergebnis stammt aus dem Jahr 2009, einige Dinge wurden in letzter Zeit geändert.
Sergey
11
Tatsächlich. Diese StackOverflow-Frage ist jetzt das erste Ergebnis bei Google.
Mydoghaswürmer

Antworten:

273

Angenommen, wir machen hier nur Textdateien und keine verrückten Binärdateien.

Nummer 1: Wie man eine ganze Datei in den Speicher liest.

(slurp "/tmp/test.txt")

Nicht zu empfehlen, wenn es sich um eine wirklich große Datei handelt.

Nummer 2: Wie man eine Datei Zeile für Zeile liest.

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

Das with-openMakro sorgt dafür, dass der Leser am Ende des Körpers geschlossen ist. Die Reader-Funktion zwingt eine Zeichenfolge (sie kann auch eine URL usw. ausführen) in eine BufferedReader. line-seqliefert eine faule seq. Das Anfordern des nächsten Elements der Lazy Seq führt dazu, dass eine Zeile vom Leser gelesen wird.

Beachten Sie, dass Sie ab Clojure 1.7 auch Wandler zum Lesen von Textdateien verwenden können.

Nummer 3: Wie schreibe ich in eine neue Datei?

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

Auch hier ist darauf zu with-openachten, dass das BufferedWriteram Ende des Körpers geschlossen ist. Der Writer erzwingt einen String in einen BufferedWriter, den Sie über Java Interop verwenden:(.write wrtr "something").

Sie könnten auch spitdas Gegenteil von verwenden slurp:

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

Nummer 4: Fügen Sie eine Zeile an eine vorhandene Datei an.

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

Wie oben, jetzt jedoch mit Option zum Anhängen.

Oder nochmal mit spitdem Gegenteil von slurp:

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

PS: Um genauer zu sagen, dass Sie in eine Datei lesen und schreiben und nicht in etwas anderes, können Sie zuerst ein Dateiobjekt erstellen und es dann in ein BufferedReaderoder Writer zwingen :

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

Die Dateifunktion befindet sich auch in clojure.java.io.

PS2: Manchmal ist es praktisch, das aktuelle Verzeichnis (also ".") Zu sehen. Sie können den absoluten Pfad auf zwei Arten erhalten:

(System/getProperty "user.dir") 

oder

(-> (java.io.File. ".") .getAbsolutePath)
Michiel Borkent
quelle
1
Vielen Dank für Ihre ausführliche Antwort. Ich bin froh, die empfohlene Art der Datei-E / A (Textdatei) in 1.3 kennenzulernen. Es scheint einige Bibliotheken über Datei-E / A gegeben zu haben (clojure.contrb.io, clojure.contrib.duck-Streams und einige Beispiele, die Java BufferedReader FileInputStream InputStreamReader direkt verwenden), was mich verwirrender machte. Darüber hinaus gibt es wenig Informationen über Clojure 1.3, insbesondere auf Japanisch (meine natürliche Sprache). Vielen Dank.
Jolly-San
Hallo Jolly-San, tnx für die Annahme meiner Antwort! Zu Ihrer Information, clojure.contrib.duck-Streams ist jetzt veraltet. Dies trägt möglicherweise zur Verwirrung bei.
Michiel Borkent
Das heißt, (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))Ausbeuten IOException Stream closedstatt einer Sammlung von Linien. Was ist zu tun? Ich bekomme aber gute Ergebnisse mit der Antwort von @satyagraha.
0dB
4
Das hat mit Faulheit zu tun. Wenn Sie das Ergebnis von line-seq außerhalb von with-open verwenden, was passiert, wenn Sie das Ergebnis auf die REPL drucken, ist der Reader bereits geschlossen. Eine Lösung besteht darin, die Zeilenfolge in eine Tür einzuschließen, wodurch die Auswertung sofort erzwungen wird. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent
Beachten Sie für die tatsächlichen Anfänger wie mich, dass doseqRückgaben nilzu traurigen Zeiten ohne Rückgabewert führen können.
Oktober
33

Wenn die Datei in den Speicher passt, können Sie sie mit Slurp und Spit lesen und schreiben:

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

(s enthält jetzt den Inhalt einer Datei als Zeichenfolge)

(spit "newfile.txt" s)

Dadurch wird newfile.txt erstellt, wenn es nicht beendet wird und den Dateiinhalt schreibt. Wenn Sie an die Datei anhängen möchten, können Sie dies tun

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

Um eine Datei zeilenweise zu lesen oder zu schreiben, verwenden Sie den Reader und Writer von Java. Sie werden in den Namespace clojure.java.io eingeschlossen:

(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!"

Beachten Sie, dass der Unterschied zwischen Slurp / Spit und den Reader / Writer-Beispielen darin besteht, dass die Datei in letzteren offen bleibt (in den let-Anweisungen) und das Lesen und Schreiben gepuffert wird, was beim wiederholten Lesen / Schreiben in eine Datei effizienter ist.

Hier gibt es mehr Informationen: schlürfen spucken clojure.java.io Java BufferedReader Java Writer

Paul
quelle
1
Danke Paul. Ich könnte durch Ihre Codes und Ihre Kommentare mehr erfahren, die in dem Punkt klar sind, der sich auf die Beantwortung meiner Frage konzentriert. Vielen Dank.
Jolly-San
Vielen Dank, dass Sie Informationen zu Methoden auf etwas niedrigerer Ebene hinzugefügt haben, die in Michiel Borkents (ausgezeichneter) Antwort auf die besten Methoden für typische Fälle nicht enthalten sind.
Mars
@ Mars Danke. Eigentlich habe ich diese Frage zuerst beantwortet, aber Michiels Antwort hat mehr Struktur und das scheint sehr beliebt zu sein.
Paul
Er macht einen guten Job mit den üblichen Fällen, aber Sie haben andere Informationen geliefert. Deshalb ist es gut, dass SE mehrere Antworten zulässt.
Mars
6

In Bezug auf Frage 2 möchte man manchmal, dass der Zeilenstrom als erstklassiges Objekt zurückgegeben wird. Um dies als verzögerte Sequenz zu erhalten und die Datei auf EOF immer noch automatisch geschlossen zu haben, habe ich diese Funktion verwendet:

(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
quelle
7
Ich denke nicht, dass Nesting defnideomatisch ist. Ihr read-next-line, soweit ich das verstehe, ist sichtbar außerhalb Ihrer read-linesFunktion. Möglicherweise haben Sie (let [read-next-line (fn [] ...))stattdessen eine verwendet.
Kristianlm
Ich denke, Ihre Antwort wäre ein bisschen besser, wenn sie die erstellte Funktion (Schließen über den offenen Reader) zurückgeben würde, als global bindend.
Ward
1

So lesen Sie die gesamte Datei.

Wenn sich die Datei im Ressourcenverzeichnis befindet, können Sie Folgendes tun:

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

Denken Sie daran, zu verlangen / zu verwenden clojure.java.io.

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

Um eine Datei Zeile für Zeile zu lesen, müssen Sie nicht mehr auf Interop zurückgreifen:

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

Dies setzt voraus, dass Ihre Datendatei im Ressourcenverzeichnis gespeichert ist und dass die erste Zeile Header-Informationen enthält, die verworfen werden können.

Chris Murphy
quelle