8.6 I/O Patterns🔗

(require racket/port) is needed for #lang racket/base.

For these examples, say you have two files in the same directory as your program, "oneline.txt" and "manylines.txt".


 I am one line, but there is an empty line after this one.



 I am

 a message

 split over a few lines.


If you have a file that is quite small, you can get away with reading in the file as a string:

> (define file-contents
    (port->string (open-input-file "oneline.txt") #:close? #t))
> (string-suffix? file-contents "after this one.")


> (string-suffix? file-contents "after this one.\n")


> (string-suffix? (string-trim file-contents) "after this one.")


We use port->string from racket/port to do the reading to a string: the #:close? #t keyword argument ensures that our file is closed after the read.

We use string-trim from racket/string to remove any extraneous whitespace at the very beginning and very end of our file. (Lots of formatters out there insist that text files end with a single blank line).

See also read-line if your file has one line of text.

If, instead, you want to process individual lines of a file, then you can use for with in-lines:

> (define (upcase-all in)
    (for ([l (in-lines in)])
      (display (string-upcase l))
> (upcase-all (open-input-string
                "Hello, World!\n"
                "Can you hear me, now?")))



You could also combine computations over each line. So if you want to know how many lines contain “m”, you could do:

> (with-input-from-file "manylines.txt"
    (lambda ()
      (for/sum ([l (in-lines)]
                 #:when (string-contains? l "m"))


Here, with-input-from-file from racket/port sets the default input port to be the file "manylines.txt" inside the thunk. It also closes the file after the computation has been completed (and in a few other cases).

However, if you want to determine whether “hello” appears in a file, then you could search separate lines, but it’s even easier to simply apply a regular expression (see Regular Expressions) to the stream:

> (define (has-hello? in)
    (regexp-match? #rx"hello" in))
> (has-hello? (open-input-string "hello"))


> (has-hello? (open-input-string "goodbye"))


If you want to copy one port into another, use copy-port from racket/port, which efficiently transfers large blocks when lots of data is available, but also transfers small blocks immediately if that’s all that is available:

> (define o (open-output-string))
> (copy-port (open-input-string "broom") o)
> (get-output-string o)