Di Clojure 1.3, Cara membaca dan menulis file

163

Saya ingin tahu cara "disarankan" membaca dan menulis file di clojure 1.3.

  1. Cara membaca seluruh file
  2. Cara membaca file baris demi baris
  3. Cara menulis file baru
  4. Cara menambahkan baris ke file yang ada
riang-san
sumber
2
Hasil pertama dari google: lethain.com/reading-file-in-clojure
jcubic
8
Hasil ini dari 2009, beberapa hal telah berubah akhir-akhir ini.
Sergey
11
Memang. Pertanyaan StackOverflow ini sekarang adalah hasil pertama di Google.
mydoghasworms

Jawaban:

273

Dengan asumsi kita hanya melakukan file teks di sini dan bukan beberapa hal biner gila.

Nomor 1: cara membaca seluruh file ke dalam memori.

(slurp "/tmp/test.txt")

Tidak disarankan saat ini adalah file yang sangat besar.

Nomor 2: cara membaca file baris demi baris.

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

The with-openmakro mengurus bahwa pembaca ditutup pada akhir tubuh. Fungsi pembaca memaksa sebuah string (itu juga dapat melakukan URL, dll) menjadi a BufferedReader. line-seqmemberikan seq malas. Menuntut elemen berikutnya dari hasil malas menjadi baris yang sedang dibaca dari pembaca.

Perhatikan bahwa dari Clojure 1.7 dan selanjutnya, Anda juga dapat menggunakan transduser untuk membaca file teks.

Nomor 3: cara menulis ke file baru.

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

Sekali lagi, with-openberhati-hatilah agar BufferedWriterditutup di ujung tubuh. Writer memaksa string menjadi BufferedWriter, yang Anda gunakan gunakan melalui java interop:(.write wrtr "something").

Anda juga bisa menggunakan spit, kebalikan dari slurp:

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

Nomor 4: tambahkan baris ke file yang ada.

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

Sama seperti di atas, tetapi sekarang dengan opsi tambahkan.

Atau lagi dengan spit, kebalikan dari slurp:

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

PS: Untuk lebih eksplisit tentang fakta bahwa Anda membaca dan menulis ke File dan bukan sesuatu yang lain, Anda bisa membuat objek File terlebih dahulu dan kemudian memaksa menjadi BufferedReaderatau Penulis:

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

Fungsi file juga di clojure.java.io.

PS2: Kadang-kadang berguna untuk dapat melihat apa direktori saat ini (jadi ".") Adalah. Anda bisa mendapatkan jalur absolut dengan dua cara:

(System/getProperty "user.dir") 

atau

(-> (java.io.File. ".") .getAbsolutePath)
Michiel Borkent
sumber
1
Terima kasih banyak atas jawaban terinci Anda. Saya senang mengetahui cara direkomendasikan File IO (file teks) di 1.3. Tampaknya ada beberapa perpustakaan tentang File IO (clojure.contrb.io, clojure.contrib.duck-stream dan beberapa contoh langsung menggunakan Java BufferedReader FileInputStream InputStreamReader) yang membuat saya lebih membingungkan. Selain itu ada sedikit informasi tentang Clojure 1.3 terutama dalam bahasa Jepang (bahasa alami saya) Terima kasih.
jolly-san
Hai jolly-san, tnx untuk menerima jawaban saya! Untuk informasi Anda, clojure.contrib.duck-stream sekarang tidak digunakan lagi. Ini mungkin menambah kebingungan.
Michiel Borkent
Artinya, (with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))menghasilkan IOException Stream closedbukan kumpulan garis. Apa yang harus dilakukan? Saya mendapatkan hasil yang baik dengan jawaban oleh @ satyagraha.
0dB
4
Ini ada hubungannya dengan kemalasan. Ketika Anda menggunakan hasil baris-seq di luar dengan-terbuka, yang terjadi ketika Anda mencetak hasilnya ke REPL, maka pembaca sudah ditutup. Solusi adalah dengan membungkus baris-seq di dalam sebuah doall, yang memaksa evaluasi segera. (with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
Michiel Borkent
Untuk pemula yang sebenarnya seperti saya, perhatikan bahwa doseqpengembalian nilyang dapat menyebabkan waktu sedih-tidak-kembali-nilai.
Oktober
33

Jika file tersebut sesuai dengan memori, Anda dapat membaca dan menulisnya dengan slurp dan meludah:

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

(sekarang berisi konten file sebagai string)

(spit "newfile.txt" s)

Ini menciptakan newfile.txt jika tidak keluar dan menulis konten file. Jika Anda ingin menambahkan file yang dapat Anda lakukan

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

Untuk membaca atau menulis file secara linier, Anda akan menggunakan pembaca dan penulis Java. Mereka dibungkus dalam namespace 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!"

Perhatikan bahwa perbedaan antara contoh slurp / spit dan pembaca / penulis adalah bahwa file tetap terbuka (dalam pernyataan let) di yang terakhir dan membaca dan menulis buffer, sehingga lebih efisien ketika berulang kali membaca dari / menulis ke file.

Berikut ini informasi lebih lanjut: slurp spit clojure.java.io Java BufferedReader Java's Writer

Paul
sumber
1
Paul terima kasih. Saya bisa belajar lebih banyak dengan kode Anda dan komentar Anda yang jelas pada titik fokus pada menjawab pertanyaan saya. Terima kasih banyak.
jolly-san
Terima kasih telah menambahkan informasi tentang metode tingkat sedikit lebih rendah yang tidak diberikan dalam jawaban (luar biasa) Michiel Borkent tentang metode terbaik untuk kasus-kasus tertentu.
Mars
@ Mars, terima kasih. Sebenarnya saya memang menjawab pertanyaan ini terlebih dahulu, tetapi jawaban Michiel memiliki lebih banyak struktur dan itu tampaknya sangat populer.
Paul
Ia melakukan pekerjaannya dengan baik pada kasus-kasus yang biasa, tetapi Anda memberikan informasi lain. Itu sebabnya bagus kalau SE memungkinkan banyak jawaban.
Mars
6

Mengenai pertanyaan 2, seseorang terkadang ingin aliran garis dikembalikan sebagai objek kelas satu. Untuk mendapatkan ini sebagai urutan malas, dan masih memiliki file ditutup secara otomatis pada EOF, saya menggunakan fungsi ini:

(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
sumber
7
Saya tidak berpikir bersarang defnadalah Clojure yang ideomatik. Anda read-next-line, sejauh yang saya mengerti, terlihat di luar read-linesfungsi Anda . Anda mungkin menggunakan yang (let [read-next-line (fn [] ...))sebaliknya.
kristianlm
Saya pikir jawaban Anda akan sedikit lebih baik jika mengembalikan fungsi yang dibuat, (menutup pembaca terbuka) daripada mengikat secara global.
Ward
1

Ini adalah cara membaca seluruh file.

Jika file ada di direktori sumber daya, Anda dapat melakukan ini:

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

ingat untuk meminta / menggunakan clojure.java.io.

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

Untuk membaca file baris demi baris, Anda tidak perlu lagi melakukan interop:

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

Ini mengasumsikan bahwa file data Anda disimpan di direktori sumber daya dan bahwa baris pertama adalah informasi header yang dapat dibuang.

Chris Murphy
sumber