How do I read in lines from a text file in OCaml?

痴心易碎 提交于 2019-11-30 08:20:09

If you don't have Extlib installed (and apparently you don't based on the error message above), then generally it's done something like this:

let read_file filename = 
let lines = ref [] in
let chan = open_in filename in
try
  while true; do
    lines := input_line chan :: !lines
  done; !lines
with End_of_file ->
  close_in chan;
  List.rev !lines ;;

If you do have Extlib:

let read_file filename =
  let chan = open_in filename in
  Std.input_list chan

...which is pretty much what you have.

If you have the Batteries-included library you could read a file into an Enum.t and iterate over it as follows:

let filelines = File.lines_of filename in
Enum.iter ( fun line -> (*Do something with line here*) ) filelines

If you have the OCaml Core library installed, then it is as simple as:

open Core.Std
let r file = In_channel.read_lines file

If you have corebuild installed, then you can just compile your code with it:

corebuild filename.byte

if your code resides in a file named filename.ml.

If you don't have the OCaml Core, or do not want to install it, or some other standard library implementation, then, of course, you can implement it using a vanilla OCaml's standard library. There is a function input_line, defined in the Pervasives module, that is automatically opened in all OCaml programs (i.e. all its definitions are accessible without further clarification with a module name). This function accepts a value of type in_channel and returns a line, that was read from the channel. Using this function you can implement the required function:

let read_lines name : string list =
  let ic = open_in name in
  let try_read () =
    try Some (input_line ic) with End_of_file -> None in
  let rec loop acc = match try_read () with
    | Some s -> loop (s :: acc)
    | None -> close_in ic; List.rev acc in
  loop []

This implementation uses recursion, and is much more natural to OCaml programming.

Here's a recursive solution using Scanf:

let read_all_lines file_name =
  let in_channel = open_in file_name in
  let rec read_recursive lines =
    try
      Scanf.fscanf in_channel "%[^\r\n]\n" (fun x -> read_recursive (x :: lines))
    with
      End_of_file ->
        lines in
  let lines = read_recursive [] in
  let _ = close_in_noerr in_channel in
  List.rev (lines);;

Usage:

let all_lines = read_all_lines "input.txt";;

However, I'd prefer to stream line-by-line:

let make_reader file_name =
  let in_channel = open_in file_name in
  let closed = ref false in
  let read_next_line = fun () ->
    if !closed then
      None
    else
      try
        Some (Scanf.fscanf in_channel "%[^\r\n]\n" (fun x -> x))
      with
        End_of_file ->
          let _ = close_in_noerr in_channel in
          let _ = closed := true in
          None in
  read_next_line;;

Usage:

let read_next = make_reader "input.txt";;
let next_line = read_next ();;

And may be a bit of icing:

type reader = {read_next : unit -> string option};;

let make_reader file_name =
  let in_channel = open_in file_name in
  let closed = ref false in
  let read_next_line = fun () ->
    if !closed then
      None
    else
      try
        Some (Scanf.fscanf in_channel "%[^\r\n]\n" (fun x -> x))
      with
        End_of_file ->
          let _ = close_in_noerr in_channel in
          let _ = closed := true in
          None in
  {read_next = read_next_line};;

Usage:

let r = make_reader "input.txt";;
let next_line = r.read_next ();;

Hope this helps!

Another style to read lines from a file using Scanf "string indiciation" and zero-width character. It is like traditional imperative style.

open Scanf 
open Printf

(* little helper functions *)
let id x = x 
let const x = fun _ -> x
let read_line file = fscanf file "%s@\n" id 
let is_eof file = try fscanf file "%0c" (const false) with End_of_file -> true

let _ = 
  let file = open_in "/path/to/file" in 

  while not (is_eof file) do 
    let s = read_line file in
    (* do something with s *) 
    printf "%s\n" s 
  done;

  close_in file

NOTE:

  1. read_line ignore one trailing \n, so if the last character of your file is \n, it may seems like you have missed the last empty line.
  2. when using of Scanf, due to bufferization, do not mix other low level reading on the same channel, otherwise it will result in strange behaviour.

This reads the file's lines and prints each of them:

open Core.Std

let handle_line line =
  printf "Your line: %s \n" line

let () =
  let file_to_read = "./file_to_read.txt" in
    let lines = In_channel.read_lines file_to_read in
      List.iter ~f: handle_line lines

Here is a simple recursive solution that does not accumulate the lines or use external libraries, but lets you read a line, process it using a function, read the next recursively until done, then exit cleanly. The exit function closes the open filehandle and signals success to the calling program.

let read_lines file process =
  let in_ch = open_in file in
  let rec read_line () =
    let line = try input_line in_ch with End_of_file -> exit 0
    in (* process line in this block, then read the next line *)
       process line;
       read_line ();
in read_line ();;

read_lines some_file print_endline;;

Std.input_list apparently requires Extlib, which you should install on your system (libextlib-ocaml and libextlib-ocaml-dev on Debian systems).

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!