In Clojure 1.3, Come leggere e scrivere un file


163

Mi piacerebbe conoscere il modo "raccomandato" di leggere e scrivere un file in clojure 1.3.

  1. Come leggere l'intero file
  2. Come leggere un file riga per riga
  3. Come scrivere un nuovo file
  4. Come aggiungere una linea a un file esistente

2
Primo risultato da Google: lethain.com/reading-file-in-clojure
jcubic,

8
Questo risultato è del 2009, alcune cose sono state cambiate di recente.
Sergey,

11
Infatti. Questa domanda StackOverflow è ora il primo risultato su Google.
mydoghasworms

Risposte:


273

Supponendo che stiamo facendo solo file di testo qui e non alcune cose binarie pazze.

Numero 1: come leggere un intero file in memoria.

(slurp "/tmp/test.txt")

Non raccomandato quando si tratta di un file molto grande.

Numero 2: come leggere un file riga per riga.

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

La with-openmacro si preoccupa che il lettore sia chiuso alla fine del corpo. La funzione reader impone una stringa (può anche fare un URL, ecc.) In a BufferedReader. line-seqoffre un seq pigro. Esigere il prossimo elemento del seq pigro risulta in una riga che viene letta dal lettore.

Nota che da Clojure 1.7 in poi, puoi anche usare i trasduttori per leggere file di testo.

Numero 3: come scrivere in un nuovo file.

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

Ancora una volta, si with-openprende cura che BufferedWritersia chiuso alla fine del corpo. Writer costringe una stringa in un BufferedWriter, che usi usare tramite java interop:(.write wrtr "something").

Puoi anche usare spitl'opposto di slurp:

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

Numero 4: aggiungi una riga a un file esistente.

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

Come sopra, ma ora con l'opzione Aggiungi.

O ancora con spit, l'opposto di slurp:

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

PS: Per essere più esplicito sul fatto che stai leggendo e scrivendo su un File e non qualcos'altro, potresti prima creare un oggetto File e poi forzarlo in un BufferedReadero Writer:

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

La funzione file è anche in clojure.java.io.

PS2: A volte è utile poter vedere qual è la directory corrente (quindi "."). Puoi ottenere il percorso assoluto in due modi:

(System/getProperty "user.dir") 

o

(-> (java.io.File. ".") .getAbsolutePath)

1
Grazie mille per la tua risposta dettagliata. Sono felice di conoscere il modo consigliato di File IO (file di testo) in 1.3. Sembra che ci siano state alcune librerie su File IO (clojure.contrb.io, clojure.contrib.duck-streams e alcuni esempi che utilizzano direttamente Java BufferedReader FileInputStream InputStreamReader) che mi hanno reso più confuso. Inoltre ci sono poche informazioni su Clojure 1.3 specialmente in giapponese (la mia lingua naturale) Grazie.
jolly-san,

Ciao jolly-san, grazie per aver accettato la mia risposta! Per tua informazione, clojure.contrib.duck-streams è ora deprecato. Questo probabilmente aggiunge confusione.
Michiel Borkent,

Cioè, (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))produce IOException Stream closedinvece di una raccolta di linee. Cosa fare? Sto ottenendo buoni risultati con la risposta di @satyagraha, però.
dB

4
Questo ha a che fare con la pigrizia. Quando si utilizza il risultato di line-seq al di fuori di with-open, che si verifica quando si stampa il risultato su REPL, il lettore è già chiuso. Una soluzione è quella di avvolgere la linea-seq all'interno di un doall, che forza immediatamente la valutazione. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent,

Per i principianti reali come me, nota che i doseqritorni nilpossono portare a tempi tristi senza valore.
Ottobre

33

Se il file si adatta alla memoria, puoi leggerlo e scriverlo con slurp e sputo:

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

(s ora contiene il contenuto di un file come stringa)

(spit "newfile.txt" s)

Ciò crea newfile.txt se non viene chiuso e scrive il contenuto del file. Se vuoi aggiungere al file puoi farlo

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

Per leggere o scrivere un file in modo lineare, utilizzare il lettore e lo scrittore di Java. Sono racchiusi nello spazio dei nomi 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!"

Si noti che la differenza tra slurp / spit e gli esempi di lettore / scrittore è che il file rimane aperto (nelle istruzioni let) in quest'ultimo e la lettura e la scrittura sono memorizzate, quindi più efficienti quando si leggono / scrivono ripetutamente da / scrivere su un file.

Ecco ulteriori informazioni: slurp spit clojure.java.io Java's BufferedReader Java's Writer


1
Grazie Paul. Potrei saperne di più dai tuoi codici e dai tuoi commenti che sono chiari nel punto concentrandosi sulla risposta alla mia domanda. Grazie mille.
jolly-san,

Grazie per aver aggiunto informazioni sui metodi di livello leggermente inferiore non forniti nella risposta (eccellente) di Michiel Borkent sui metodi migliori per i casi tipici.
Marte

@Mars Grazie. In realtà ho risposto prima a questa domanda, ma la risposta di Michiel ha più struttura e questo sembra essere molto popolare.
Paolo,

Fa un buon lavoro con i soliti casi, ma hai fornito altre informazioni. Ecco perché è positivo che SE consenta risposte multiple.
Marte,

6

Per quanto riguarda la domanda 2, a volte si desidera che il flusso di linee venga restituito come oggetto di prima classe. Per ottenere questo come una sequenza pigra e avere ancora il file chiuso automaticamente su EOF, ho usato questa funzione:

(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)))

7
Non credo che l'annidamento defnsia Clojure ideomatico. Il tuo read-next-line, per quanto ho capito, è visibile al di fuori della tua read-linesfunzione. Potresti aver usato un (let [read-next-line (fn [] ...))invece.
kristianlm,

Penso che la tua risposta sarebbe un po 'migliore se restituisse la funzione creata, (chiudendo sul lettore aperto) piuttosto che vincolante a livello globale.
Ward

1

Ecco come leggere l'intero file.

Se il file si trova nella directory delle risorse, è possibile farlo:

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

ricorda di richiedere / utilizzare clojure.java.io.


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

0

Per leggere un file riga per riga non è più necessario ricorrere all'interoperabilità:

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

Ciò presuppone che il file di dati sia conservato nella directory delle risorse e che la prima riga sia l'informazione dell'intestazione che può essere scartata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.