Dans Clojure 1.3, Comment lire et écrire un fichier

163

J'aimerais connaître la manière "recommandée" de lire et d'écrire un fichier dans clojure 1.3.

  1. Comment lire le fichier entier
  2. Comment lire un fichier ligne par ligne
  3. Comment écrire un nouveau fichier
  4. Comment ajouter une ligne à un fichier existant
jolly-san
la source
2
Premier résultat de google: lethain.com/reading-file-in-clojure
jcubic
8
Ce résultat date de 2009, certaines choses ont changé ces derniers temps.
Sergey
11
En effet. Cette question StackOverflow est désormais le premier résultat sur Google.
mydoghasworms

Réponses:

273

En supposant que nous ne faisons que des fichiers texte ici et pas des trucs binaires fous.

Numéro 1: comment lire un fichier entier en mémoire.

(slurp "/tmp/test.txt")

Non recommandé lorsqu'il s'agit d'un très gros fichier.

Numéro 2: comment lire un fichier ligne par ligne.

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

La with-openmacro veille à ce que le lecteur soit fermé à l'extrémité du corps. La fonction de lecture contraint une chaîne (elle peut également faire une URL, etc.) dans un fichier BufferedReader. line-seqdélivre un seq paresseux. Exiger l'élément suivant du paresseux seq entraîne la lecture d'une ligne par le lecteur.

Notez qu'à partir de Clojure 1.7, vous pouvez également utiliser des transducteurs pour lire des fichiers texte.

Numéro 3: comment écrire dans un nouveau fichier.

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

Encore une fois, with-openveille à ce que le BufferedWritersoit fermé à l'extrémité du corps. Writer contraint une chaîne en a BufferedWriter, que vous utilisez via java interop:(.write wrtr "something").

Vous pouvez également utiliser spit, l'opposé de slurp:

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

Numéro 4: ajoutez une ligne à un fichier existant.

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

Identique à ci-dessus, mais maintenant avec l'option d'ajout.

Ou encore avec spit, l'opposé de slurp:

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

PS: Pour être plus explicite sur le fait que vous lisez et écrivez dans un fichier et pas autre chose, vous pouvez d'abord créer un objet File, puis le forcer dans un BufferedReaderou Writer:

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

La fonction de fichier est également dans clojure.java.io.

PS2: Parfois, il est pratique de voir quel est le répertoire actuel (donc "."). Vous pouvez obtenir le chemin absolu de deux manières:

(System/getProperty "user.dir") 

ou

(-> (java.io.File. ".") .getAbsolutePath)
Michiel Borkent
la source
1
Merci beaucoup pour votre réponse détaillée. Je suis heureux de connaître la manière recommandée de File IO (fichier texte) dans 1.3. Il semble y avoir eu des bibliothèques sur File IO (clojure.contrb.io, clojure.contrib.duck-streams et quelques exemples utilisant directement Java BufferedReader FileInputStream InputStreamReader), ce qui m'a rendu plus déroutant. De plus il y a peu d'informations sur Clojure 1.3 surtout en japonais (ma langue naturelle) Merci.
jolly-san
Salut jolly-san, tnx pour avoir accepté ma réponse! Pour votre information, clojure.contrib.duck-streams est désormais obsolète. Cela ajoute peut-être à la confusion.
Michiel Borkent
Autrement dit, (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))cède IOException Stream closedau lieu d'une collection de lignes. Que faire? J'obtiens de bons résultats avec la réponse de @satyagraha, cependant.
0 dB
4
Cela a à voir avec la paresse. Lorsque vous utilisez le résultat de line-seq en dehors de with-open, ce qui se produit lorsque vous imprimez son résultat sur le REPL, le lecteur est déjà fermé. Une solution consiste à envelopper le line-seq dans un doall, ce qui force l'évaluation immédiatement. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent
Pour les débutants comme moi, notez que les doseqretours nilpeuvent conduire à des temps tristes sans valeur de retour.
oct
33

Si le fichier tient dans la mémoire, vous pouvez le lire et l'écrire avec slurp et spit:

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

(s contient maintenant le contenu d'un fichier sous forme de chaîne)

(spit "newfile.txt" s)

Cela crée newfile.txt s'il ne se ferme pas et écrit le contenu du fichier. Si vous souhaitez ajouter au fichier, vous pouvez faire

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

Pour lire ou écrire un fichier par ligne, vous utiliseriez le lecteur et l'écrivain Java. Ils sont enveloppés dans l'espace de noms 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!"

Notez que la différence entre slurp / spit et les exemples de lecture / écriture est que le fichier reste ouvert (dans les instructions let) dans ce dernier et que la lecture et l'écriture sont tamponnées, donc plus efficaces lors de la lecture / écriture répétée dans un fichier.

Voici plus d'informations: slurp spit clojure.java.io Java's BufferedReader Java's Writer

Paul
la source
1
Merci Paul. Je pourrais en apprendre plus par vos codes et vos commentaires qui sont clairs dans le point de répondre à ma question. Merci beaucoup.
jolly-san
Merci d'avoir ajouté des informations sur des méthodes de niveau légèrement inférieur qui ne sont pas données dans la (excellente) réponse de Michiel Borkent sur les meilleures méthodes pour les cas typiques.
Mars
@Mars Merci. En fait, j'ai répondu à cette question en premier, mais la réponse de Michiel a plus de structure et cela semble être très populaire.
Paul
Il fait un bon travail avec les cas habituels, mais vous avez fourni d'autres informations. C'est pourquoi il est bon que SE permette plusieurs réponses.
Mars
6

Concernant la question 2, on souhaite parfois que le flux de lignes soit renvoyé comme un objet de première classe. Pour obtenir cela comme une séquence paresseuse et que le fichier soit toujours fermé automatiquement sur EOF, j'ai utilisé cette fonction:

(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
la source
7
Je ne pense pas que la nidification defnsoit un Clojure idéomatique. Votre read-next-line, pour autant que je sache, est visible en dehors de votre read-linesfonction. Vous avez peut-être utilisé un (let [read-next-line (fn [] ...)).
kristianlm
Je pense que votre réponse serait un peu meilleure si elle renvoyait la fonction créée (se fermant sur le lecteur ouvert) plutôt que globalement contraignante.
Quartier
1

Voici comment lire le fichier entier.

Si le fichier se trouve dans le répertoire des ressources, vous pouvez le faire:

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

n'oubliez pas d'exiger / d'utiliser clojure.java.io.

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

Pour lire un fichier ligne par ligne, vous n'avez plus besoin de recourir à l'interopérabilité:

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

Cela suppose que votre fichier de données est conservé dans le répertoire des ressources et que la première ligne contient des informations d'en-tête qui peuvent être supprimées.

Chris Murphy
la source