Eu gostaria de saber a maneira "recomendada" de ler e gravar um arquivo no clojure 1.3.
- Como ler o arquivo inteiro
- Como ler um arquivo linha por linha
- Como escrever um novo arquivo
- Como adicionar uma linha a um arquivo existente
Eu gostaria de saber a maneira "recomendada" de ler e gravar um arquivo no clojure 1.3.
Respostas:
Supondo que estamos apenas fazendo arquivos de texto aqui e não algumas coisas binárias loucas.
Número 1: como ler um arquivo inteiro na memória.
(slurp "/tmp/test.txt")
Não recomendado quando se trata de um arquivo muito grande.
Número 2: como ler um arquivo linha por linha.
(use 'clojure.java.io)
(with-open [rdr (reader "/tmp/test.txt")]
(doseq [line (line-seq rdr)]
(println line)))
A with-open
macro cuida para que o leitor esteja fechado no final do corpo. A função de leitor força uma string (também pode fazer uma URL, etc) em uma BufferedReader
. line-seq
oferece um seq preguiçoso. Exigir o próximo elemento do preguiçoso seq resulta em uma linha que está sendo lida pelo leitor.
Observe que, a partir do Clojure 1.7, você também pode usar transdutores para ler arquivos de texto.
Número 3: como gravar em um novo arquivo.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt")]
(.write wrtr "Line to be written"))
Mais uma vez, with-open
cuide para que o mesmo BufferedWriter
esteja fechado no final do corpo. O Writer força uma string em a BufferedWriter
, que você usa por meio da interoperabilidade java:(.write wrtr "something").
Você também pode usar spit
o oposto de slurp
:
(spit "/tmp/test.txt" "Line to be written")
Número 4: acrescente uma linha a um arquivo existente.
(use 'clojure.java.io)
(with-open [wrtr (writer "/tmp/test.txt" :append true)]
(.write wrtr "Line to be appended"))
O mesmo que acima, mas agora com a opção de acréscimo.
Ou ainda com spit
o oposto de slurp
:
(spit "/tmp/test.txt" "Line to be written" :append true)
PS: Para ser mais explícito sobre o fato de você estar lendo e gravando em um arquivo e não outra coisa, você pode primeiro criar um objeto File e depois forçá-lo a um BufferedReader
ou Writer:
(reader (file "/tmp/test.txt"))
;; or
(writer (file "tmp/test.txt"))
A função de arquivo também está em clojure.java.io.
PS2: Às vezes, é útil poder ver qual é o diretório atual (então "."). Você pode obter o caminho absoluto de duas maneiras:
(System/getProperty "user.dir")
ou
(-> (java.io.File. ".") .getAbsolutePath)
(with-open [rdr (reader "/tmp/test.txt")] (line-seq rdr))
produz em IOException Stream closed
vez de uma coleção de linhas. O que fazer? Estou obtendo bons resultados com a resposta de @satyagraha, no entanto.
(with-open [rdr (reader "/tmp/test.txt")] (doall (line-seq rdr)))
doseq
retornos nil
que podem levar a tempos tristes sem valor de retorno.
Se o arquivo couber na memória, você pode ler e escrever com slurp e spit:
(def s (slurp "filename.txt"))
(s agora contém o conteúdo de um arquivo como uma string)
(spit "newfile.txt" s)
Isso cria o newfile.txt se ele não sair e grava o conteúdo do arquivo. Se você deseja anexar ao arquivo, pode fazer
(spit "filename.txt" s :append true)
Para ler ou gravar um arquivo em linha, você usaria o leitor e gravador de Java. Eles estão agrupados no 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!"
Observe que a diferença entre os exemplos slurp / spit e leitor / gravador é que o arquivo permanece aberto (nas instruções let) no último e a leitura e gravação são armazenadas em buffer, tornando-se mais eficiente ao ler / gravar repetidamente em um arquivo.
Aqui estão mais informações: slurp spit clojure.java.io BufferedReader do Java Writer de Java
Em relação à pergunta 2, às vezes se deseja que o fluxo de linhas retorne como um objeto de primeira classe. Para obter isso como uma sequência lenta, e ainda ter o arquivo fechado automaticamente no EOF, usei esta função:
(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)))
defn
é Clojure ideomatic. Seu read-next-line
, pelo que entendi, é visível fora de sua read-lines
função. Você pode ter usado um em (let [read-next-line (fn [] ...))
vez disso.
(require '[clojure.java.io :as io])
(io/copy (io/file "/etc/passwd") \*out*\)
Para ler um arquivo linha por linha, você não precisa mais recorrer à interoperabilidade:
(->> "data.csv"
io/resource
io/reader
line-seq
(drop 1))
Isso pressupõe que seu arquivo de dados seja mantido no diretório de recursos e que a primeira linha seja informações de cabeçalho que podem ser descartadas.