How do I read a file line by line in EK9?

← File I/O · Ref: Q154

EK9 treats file input as a stream source. Open a file with TextFile.input() inside a try block, then use cat to pipe lines through stream operations like filter, map, and collect.

STREAMING FILE INPUT

Open the file and stream its contents:

  file <- TextFile(FileSystemPath().inCurrentWorkingDirectory("data.txt"))
  try
    -> input <- file.input()
    if input?
      cat input > stdout

Each line from the file flows through the pipeline as a String. A TextFile of a missing path (or a relative one: TextFile("data.txt") is never resolved against the working directory) is unset and so is its input(); the try body still runs, which is why it checks input? first.

MAKING SURE THE FILE EXISTS

FileSystemPath.createFileIfAbsent() creates an empty file when needed and returns a Result that must be checked:

  path <- FileSystemPath("data.txt")
  created <- path.createFileIfAbsent()

CAT WITH FILE INPUT

The cat operation treats the input stream as a source of lines:

  cat input | map with processLine > stdout

This sends each line through the processLine function before printing.

FILTER AND TRANSFORM LINES

Combine file streaming with pipeline operations:

  cat input | filter by notEmpty | map with trimmed > stdout

This skips empty lines and trims whitespace from each remaining line. Both stages need functions: notEmpty must test 'line is not empty', because an empty line "" is SET and 'line?' would let it through; trimmed wraps the String method trim(), which cannot be named in 'map with' directly:

  notEmpty() as pure
    -> line as String
    <- rtn as Boolean: line is not empty
  trimmed() as pure
    -> line as String
    <- rtn as String: line.trim()

COLLECT LINES INTO LIST

Gather all lines into a collection:

  allLines <- cat input | collect as List of String

This reads every line and stores them in a List for further processing.

See Q89 for stream pipelines. See Q151 for reading files. See Q125 for head/tail/skip.

Example

defines module qa.fileio.lines

  defines function

    notEmpty() as pure
      -> line as String
      <- rtn as Boolean: line is not empty

    trimmed() as pure
      -> line as String
      <- rtn as String: line.trim()

    addLineNumber() as pure
      -> line as String
      <- rtn as String: ">> " + line

  defines program

    ReadFileLinesDemo()
      stdout <- Stdout()

      // === TEXTFILE FOR LINE READING ===

      // createFileIfAbsent returns a Result that must be checked
      path <- FileSystemPath("/tmp/qa-example-lines.txt")
      created <- path.createFileIfAbsent()
      stdout.println(`File ready: ${created.isOk()}`)

      file <- TextFile(path)
      stdout.println("File for line reading: " + $file)

      // Three lines, the middle one empty
      try
        -> output <- file.output()
        if output?
          output.println("line one")
          output.println("")
          output.println("  line three  ")

      // === FILTER AND TRANSFORM ===

      // An empty line "" is SET, so notEmpty tests 'is not empty', not '?'
      try
        -> input <- file.input()
        if input?
          cat input | filter by notEmpty | map with trimmed | map with addLineNumber > stdout

      // === COLLECT INTO LIST ===

      try
        -> input <- file.input()
        if input?
          allLines <- cat input | collect as List of String
          stdout.println(`Lines collected: ${length allLines}`)

      stdout.println("Line-by-line processing uses cat with file input streams")

Common mistakes

E50060 — TextFile has no toString() method. Use the $ prefix operator for string conversion. See ek9 -h E50060 for details.

Incorrect:

stdout.println(file.toString())

Correct:

stdout.println("File for line reading: " + $file)

E50060 — TextFile has no readLines() method. Use file.input() inside a try block to get a stream for line-by-line processing. See ek9 -h E50060 for details.

Incorrect:

stdout.println(file.readLines())

Correct:

stdout.println("Line-by-line processing uses cat with file input streams")
Other ways to ask this
  • How do I process a file line by line in EK9?
  • How do I stream file contents in EK9?
  • How do I use cat with file input in EK9?

Coming from another language?

Java: Files.lines(Path) returns a Stream of String for lazy line-by-line processing. Python: for line in open('file') iterates lazily. Rust: BufRead::lines() returns an iterator of lines. Go: bufio.Scanner for line-by-line reading. EK9: cat input with TextFile.input() in a stream pipeline, supporting filter, map, collect and all stream operations.

Keywords: lines, filter, read, cat, line, pipeline, write, file, stream, iterate