Read a file in clojure and display content both using command prompt - clojure

I have read the file as follows
(defn get-lines [file]
(with-open [rdr (clojure.java.io/reader file)]
(count (line-seq rdr))))
(get-lines "D:/clojurefile/t2/ct.txt")
but it does not display the content
I want to print the data in my file on prompt!

(count (line-seq rdr)) returns the number of lines in the file. You should remove count to have the entire contents of the file printed:
(defn get-lines [file]
(with-open [rdr (clojure.java.io/reader file)]
(line-seq rdr)))
(println (get-lines "D:/clojurefile/t2/ct.txt"))
Edit: to print the contents of the file from command line (rather that in the REPL) you should use a print function - edited above. Also, if the file is not too large, it will be simpler to use the slurp function, which reads the file's contents in the memory at once, instead of with-open:
(println (slurp "D:/clojurefile/t2/ct.txt"))

Related

writing zip file to file in Clojure

I have a method for zipping:
(defn zip-project [project-id notebooks files]
(with-open [out (ByteArrayOutputStream.)
zip (ZipOutputStream. out)]
(doseq [nb notebooks]
(.putNextEntry zip (ZipEntry. (str "Notebooks/" (:notebook/name nb) ".bkr")))
(let [nb-json (:notebook/contents nb)
bytes (.getBytes nb-json)]
(.write zip bytes))
(.closeEntry zip))
(doseq [{:keys [name content]} files]
(.putNextEntry zip (ZipEntry. (str "Files/" name)))
(io/copy content zip)
(.closeEntry zip))
(.finish zip)
(.toByteArray out)))
after I make a zip I want to save it into the file something like /tmp/sample/sample.zip, but I cannot seem to make it. here is what I am doing:
(defn create-file! [path zip]
(let [f (io/file path)]
(io/make-parents f)
(io/copy zip f)
true))
The problem is, when I run unzip from terminal it says that zip file is empty and if I unzip it using Archive utility it extracts with cpgz extension.
What am I doing wrong here?
You will need essentially 4 things
Import everything (normally you would use (ns ...) but you can run this in the repl
(import 'java.io.FileOutputStream)
(import 'java.io.BufferedOutputStream)
(import 'java.io.ZipOutputStream)
(import 'java.util.zip.ZipOutputStream)
(import 'java.util.zip.ZipEntry)
You need a way to initialize the stream. This can be done nicely with the -> macro:
(defn zip-stream
"Opens a ZipOutputStream over the given file (as a string)"
[file]
(-> (FileOutputStream. file)
BufferedOutputStream.
ZipOutputStream.))
You need a way to create/close entries in the ZipOutputStream
(defn create-zip-entry
"Create a zip entry with the given name. That will be the name of the file inside the zipped file."
[stream entry-name]
(.putNextEntry stream (ZipEntry. entry-name)))
Finally you need a way to write your content.
(defn write-to-zip
"Writes a string to a zip stream as created by zip-stream"
[stream str]
(.write stream (.getBytes str)))
Putting it all together:
(with-open [stream (zip-stream "coolio.zip")]
(create-zip-entry stream "foo1.txt")
(write-to-zip stream "Hello Foo1")
(.closeEntry stream) ;; don't forget to close entries
(create-zip-entry stream "foo2.txt")
(write-to-zip stream "Hello Foo 2")
(.closeEntry stream))
The result:

How to read n lines from a file in clojure

I want to read first n lines from a file using clojure. Here is my code:
(defn read-nth-line [file]
(with-open [rdr (reader file)]
(loop [line-number 0]
(when (< line-number 20)
(nth (line-seq rdr) line-number)
(recur (inc line-number))))))
but when I run
user=> (read-nth-line "test.txt")
IndexOutOfBoundsException clojure.lang.RT.nthFrom (RT.java:871)
I have no idea why I got such an error.
Your code produces an out-of-bounds error because you call line-seq multiple times on the same reader. If you want to get a number of lines from a reader, you should call line-seq only once, then take the desired number of lines from that sequence:
(require '[clojure.java.io :as io])
(defn lines [n filename]
(with-open [rdr (io/reader filename)]
(doall (take n (line-seq rdr)))))
Example:
(run! println (lines 20 "test.txt"))
If test.txt contains fewer than 20 lines, this will simply print all the lines in the file.

Read from a file and return output in a vector

I'm just learning clojure and trying to read in a file and do something with the returned vector of results. In this instance I'm just trying to print it out.
Below is the code in question:
(defn read_file
"Read in a file from the resources directory"
[input]
(with-open [rdr (reader input)]
(doseq [line (line-seq rdr)])))
(defn -main []
(println (read_file "resources/input.txt") ))
The println returns a "nil". What do I need to do to return "line"
If the file is not very big, you can use slurp to read the file content as a string, then split it with a specific delimiter (in this case \n).
(defn read-file [f]
(-> (slurp f)
(clojure.string/split-lines)))
doseq returns nil. It's supposed to be used when you're doing stuff in a do fashion on the elements of a sequence, so mostly side-effect stuff.
Try this:
(defn file->vec
"Read in a file from the resources directory"
[input]
(with-open [rdr (reader input)]
(into [] (line-seq rdr))))
But you shouldn't do this for big files, in those cases you don't want the whole file to sit in memory. For this reason, slurp is equally bad.

Read csv into a list in clojure

I know there are a lot of related questions, I have read them but still have not gained some fundamental understanding of how to read-process-write. Take the following function for example which uses clojure-csv library to parse a line
(defn take-csv
"Takes file name and reads data."
[fname]
(with-open [file (reader fname)]
(doseq [line (line-seq file)]
(let [record (parse-csv line)]))))
What I would like to obtain is data read into some collection as a result of (def data (take-csv "file.csv")) and later to process it. So basically my question is how do I return record or rather a list of records.
"doseq" is often used for operations with side effect. In your case to create collection of records you can use "map":
(defn take-csv
"Takes file name and reads data."
[fname]
(with-open [file (reader fname)]
(doall (map (comp first csv/parse-csv) (line-seq file)))))
Better parse the whole file at ones to reduce code:
(defn take-csv
"Takes file name and reads data."
[fname]
(with-open [file (reader fname)]
(csv/parse-csv (slurp file))))
You also can use clojure.data.csv instead of clojure-csv.core. Only should rename parse-csv to take-csv in previous function.
(defn put-csv [fname table]
(with-open [file (writer fname)]
(csv/write-csv file table)))
With all the things you can do with .csv files, I suggest using clojure-csv or clojure.data.csv. I mostly use clojure-csv to read in a .csv file.
Here are some code snippets from a utility library I use with most of my Clojure programs.
from util.core
(ns util.core
^{:author "Charles M. Norton",
:doc "util is a Clojure utilities directory"}
(:require [clojure.string :as cstr])
(:import java.util.Date)
(:import java.io.File)
(:use clojure-csv.core))
(defn open-file
"Attempts to open a file and complains if the file is not present."
[file-name]
(let [file-data (try
(slurp file-name)
(catch Exception e (println (.getMessage e))))]
file-data))
(defn ret-csv-data
"Returns a lazy sequence generated by parse-csv.
Uses open-file which will return a nil, if
there is an exception in opening fnam.
parse-csv called on non-nil file, and that
data is returned."
[fnam]
(let [csv-file (open-file fnam)
inter-csv-data (if-not (nil? csv-file)
(parse-csv csv-file)
nil)
csv-data
(vec (filter #(and pos? (count %)
(not (nil? (rest %)))) inter-csv-data))]
(if-not (empty? csv-data)
(pop csv-data)
nil)))
(defn fetch-csv-data
"This function accepts a csv file name, and returns parsed csv data,
or returns nil if file is not present."
[csv-file]
(let [csv-data (ret-csv-data csv-file)]
csv-data))
Once you've read in a .csv file, then what you do with its contents is another matter. Usually, I am taking .csv "reports" from one financial system, like property assessments, and formatting the data to be uploaded into a database of another financial system, like billing.
I will often either zipmap each .csv row so I can extract data by column name (having read in the column names), or even make a sequence of zipmap'ped .csv rows.
Just to add this good answers, here is a full example
First, add clojure-csv into your dependencies
(ns scripts.csvreader
(:require [clojure-csv.core :as csv]
[clojure.java.io :as io]))
(defn take-csv
"Takes file name and reads data."
[fname]
(with-open [file (io/reader fname)]
(-> file
(slurp)
(csv/parse-csv))))
usage
(take-csv "/path/youfile.csv")

Clojure: buffered reader in for loop

I have a large text file I want to process in Clojure.
I need to process it 2 lines at a time.
I settled on using a for loop so I could pull 2 lines for each pass with the following binding (rdr is my reader):
[[line-a line-b] (partition 2 (line-seq rdr))]
(I would be interested in knowing other ways to get 2 lines for each loop iteration but that is not the point of my question).
When trying to get the loop to work (using a simpler binding for these tests), I am seeing the following behavior that I can't explain:
Why does
(with-open [rdr (reader "path/to/file")]
(for [line (line-seq rdr)]
line))
trigger a Stream closed exception
while
(with-open [rdr (reader "path/to/file")]
(doseq [line (line-seq rdr)]
(println line)))
works?
for is lazy and just returns the head of the sequence that will eventually read the data from the file. The file is already closed when the for's contents are printed by your repl. you can fix this pu wrapping the for in a doall
(with-open [rdr (reader "path/to/file")]
(doall (for [line (line-seq rdr)]
line)))
Though this unlazys the sequence.
here is a sample of a function out of my misc.clj that lazily closes the file at it's end:
(defn byte-seq [rdr]
"create a lazy seq of bytes in a file and close the file at the end"
(let [result (. rdr read)]
(if (= result -1)
(do (. rdr close) nil)
(lazy-seq (cons result (byte-seq rdr))))))