8. File Contents

Introduction

;; open the file and loop through the port with read-line:
(let ((p (open-input-file file)))
  (do ((line (read-line p) (read-line p)))
      ((eof-object? line))
    (format #t "~A\n" (string-length line)))
  (close p))

;; you can use with-input-from-file to temporarily rebind stdin:
(with-input-from-file file
  (lambda ()
    (do ((line (read-line) (read-line)))
        ((eof-object? line))
      (format #t "~A\n" (string-length line)))))

;; or define a utility procedure to do this
(define (for-each-line proc file)
  (with-input-from-file file
    (lambda ()
      (do ((line (read-line) (read-line)))
          ((eof-object? line))
        (proc line)))))
(for-each-line (lambda (x) (format #t "~A\n" (string-length line))) file)

;; read in the file as a list of lines
(define (read-lines file)
  (let ((ls '()))
    (with-input-from-file file
      (lambda ()
        (do ((line (read-line) (read-line)))
            ((eof-object? line))
          (set! ls (cons line ls)))
        (reverse ls)))))

;; read in the file as a single string
(define (file-contents file)
  (call-with-input-file file
    (lambda (p)
      (let* ((size (stat:size (stat p)))
             (buf (make-string size)))
        (read-string!/partial buf p)
        buf))))

;; use display to print human readable output
(display '("One" "two" "three") port)  ; (One two three)
(display "Baa baa black sheep.\n")     ; Sent to default output port

;; use write to print machine readable output
(write '("One" "two" "three") port)    ; ("One" "two" "three")

;; use (ice-9 rw) to read/write fixed-length blocks of data:
(use-modules (ice-9 rw))
(let ((buffer (make-string 4096)))
  (read-string!/partial buffer port 4096))

;; truncate-file
(truncate-file port length)  ; truncate to length
(truncate-file port)         ; truncate to current pos

;; ftell
(define pos (ftell port))
(format #t "I'm ~A bytes from the start of DATAFILE.\n" pos)

;; seek
(seek log-port 0 SEEK_END)      ; seek to end
(seek data-port pos SEEK_SET)   ; seek to pos
(seek out-port -20 SEEK_CUR)    ; seek back 20 bytes

;; block read/write
(use-modules (ice-9 rw))
(write-string/partial mystring data-port (string-length mystring))
(read-string!/partial block 256 5)

Reading Lines with Continuation Characters

(let ((rx (make-regexp "(.*)\\\\$"))) ; or "(.*)\\\\\\s*$"
  (with-input-from-file file
    (lambda ()
      (let loop ((line (read-line)))
        (if (not (eof-object? line))
          (let ((m (regexp-exec rx line))
                (next (read-line)))
            (cond ((and m (not (eof-object? next)))
                   (loop (string-append (match:substring m 1) next)))
                  (else
                   ;; else process line here, then recurse
                   (loop next)))))))))

Counting Lines (or Paragraphs or Records) in a File

(do ((line (read-line p) (read-line p))
     (i 0 (1+ i)))
    ((eof-object? line) i))

;; fastest way if your terminator is a single newline
(use-modules (ice-9 rw) (srfi srfi-13))
(let ((buf (make-string (expt 2 16)))
      (count 0))
  (do ((len (read-string!/partial buf p) (read-string!/partial buf p)))
      ((not len) count)
    (set! count (+ count (string-count buf #\newline 0 len)))))

;; or use port-line
(let loop ((line (read-line p)))
  (if (eof-object? line) (port-line p) (loop (read-line p))))

Processing Every Word in a File

;; default behaviour of string-tokenize is to split on whitespace:
(use-modules (srfi srfi-13))
(let loop ((line (read-line p)))
  (cond ((not eof-object? line)
         (for-each some-function-of-word (string-tokenize line))
         (loop (read-line p)))))

(let ((table (make-hash-table 31)))
  (let loop ((line (read-line p)))
    (cond ((not (eof-object? line))
           (for-each
            (lambda (w) (hash-set! table w (1+ (hash-ref table w 0))))
            (string-tokenize line))
           (loop (read-line p)))))
  (hash-fold (lambda (k v p) (format #t "~5D ~A\n" v k)) #f table))

Reading a File Backwards by Line or Paragraph

;; build up the list the reverse it or fold over it:
(define lines (read-lines file))
(for-each (lambda (word) do-something-with-word) (reverse lines))
(fold (lambda (word acc) do-something-with-word) #f lines)

Trailing a Growing File

;; save the current position and reseek to it
(define (tail file)
  (call-with-input-file file
    (lambda (p)
      (let loop ((line (read-line p)))
        (cond ((eof-object? line)
               (sleep sometime)
               (let ((pos (ftell p)))
                 (seek p 0 SEEK_SET)
                 (seek p pos SEEK_SET)))
              (else
               ;; process line
               ))
        (loop (read-line p))))))

Picking a Random Line from a File

(let ((rand-line #f))
  (let loop ((line (read-line p)))
    (cond ((not (eof-object? line))
           (if (= 0 (random (port-line p)))
             (set! rand-line line))
           (loop (read-line p)))))
  ;; rand-line is the random line
  )

Randomizing All Lines

(define (shuffle list)
  (let ((v (list->vector list)))
    (do ((i (1- (vector-length v)) (1- i)))
        ((< i 0) (vector->list v))
      (let ((j (random (1+ i))))
        (cond ((not (= i j))
               (let ((temp (vector-ref v i)))
                 (vector-set! v i (vector-ref v j))
                 (vector-set! v j temp))))))))

(define rand-lines (shuffle (read-lines file))

Reading a Particular Line in a File

;; looking for line number desired-line-number
(do ((line (read-line p) (read-line p)))
    ((= ((port-line p) desired-line-number) line)))
;; or read into a list
(define lines (read-lines file))
(list-ref lines desired-line-number)

;; @@INCOMPLETE@@
; (define (build-index data-file index-file)
;   )

; (define (line-with-index data-file index-file line-number)
;   )

Processing Variable-Length Text Fields

;; use string-tokenize with an appropriate character set
(use-modules (srfi srfi-13) (srfi srfi-14))
(define fields (string-tokenize line (string->charset "+-")))
(define fields (string-tokenize line (string->charset ":")))
(define fields (string-tokenize line))

Removing the Last Line of a File

(let ((p (open-file file "r+")))
  (let ((pos 0))
    (let loop ((line (read-line p)))
      (cond ((eof-object? (peek-char p))
             (seek p 0 SEEK_SET)
             (truncate-file p pos)
             (close p))
            (else
             (set! pos (ftell p))
             (loop (read-line p)))))))

Processing Binary Files

;; no equivalent - don't know how Guile under windows handles this

Using Random-Access I/O

(let* ((address (* recsize recno))
       (buf (make-string recsize)))
  (seek p address SEEK_SET)
  (read-string!/partial buf p)
  buf)

Updating a Random-Access File

(let* ((address (* recsize recno))
       (buf (make-string recsize)))
  (seek p address SEEK_SET)
  (read-string!/partial buf p)
  ;; modify buf, then write back with
  (seek p address SEEK_SET)
  (write-string/partial buf p)
  (close p))

;; @@INCOMPLETE@@
;; weekearly

Reading a String from a Binary File

(seek p addr SEEK_SET)
(define str (read-delimited (make-string 1 #\nul) p))

#!/usr/local/bin/guile -s
!#
;; bgets -- get a string from an address in a binary file
(use-modules (ice-9 format))

(define args (cdr (command-line)))
(define file (car args))
(define addrs (map string->number (cdr args)))
(define delims (make-string 1 #\nul))

(call-with-input-file file
  (lambda (p)
    (for-each
     (lambda (addr)
       (seek p addr SEEK_SET)
       (format #t "~X ~O ~D ~S\n" addr addr addr
               (read-delimited delims p)))
     addrs)))

;; @@INCOMPLETE@@
;; strings

Reading Fixed-Length Records

Reading Configuration Files

Testing a File for Trustworthiness

Program: tailwtmp

Program: tctee

Program: laston