8.6 I/O Patterns

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

"oneline.txt"

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

 

"manylines.txt"

 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:

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

#f

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

#t

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

#t

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))
      (newline)))
> (upcase-all (open-input-string
               (string-append
                "Hello, World!\n"
                "Can you hear me, now?")))

HELLO, WORLD!

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:

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

2

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"))

#t

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

#f

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)

"broom"