How to create a new file while automatically creating any intermediate directories?

Hi all,

I have been trying to figure out how to create a filesystem file making sure to automatically create intermediate directories. The following snippet does not seem to work at all:

    Out_channel.with_open_gen
      Out_channel.[Open_creat; Open_wronly; Open_trunc]
      perm
      "/some/dir/more/dirs/basepath.ext"
      (fun oc -> Out_channel.output_string oc value)

I keep getting the Exception: Sys_error "/some/dir/more/dirs/basepath.ext: No such file or directory". error. Is there a way to ensure the intermediate directories /more/dirs/ get auto created if /some/dir/ already exists while requesting for /some/dir/more/dirs/basepath.ext: to be created?

1 Like

This may not be the most efficient and portable way, but you can use the following command to create an empty file and all its parent directories:

Sys.command "install -D /dev/null /some/dir/more/dirs/basepath.ext"

After that, you can write content to this file, assuming that it’s created.

I think you will need to do this ‘manually’ for each directory. Fortunately, there are helper functions in the Sys module that can help: eg file_exists, is_directory, and mkdir. I recommend a function signature like this:

val write_to : ~path:string list -> string -> unit

To be called like:

write_to ~path:["some"; "dir"; "more"; "dirs"; "basepath.ext"] value

Using this approach, I think I can make it work like so:

    let fp = "/some/dir/more/dirs/basepath.ext" in
    let pardir = Filename.dirname fp in
    if Sys.command ("mkdir -p " ^ pardir) = 0 then
      Out_channel.with_open_gen
        Out_channel.[Open_creat; Open_wronly; Open_trunc]
        perm
        fp
        (fun oc -> Out_channel.output_string oc value)
    else
        (*panic ? *)

However, I don’t like having to resort to Sys.command.

You can write a small function to do this:

let rec create_parent_dir fn =
  let parent_dir = Filename.dirname fn in
  if not (Sys.file_exists parent_dir) then begin
    create_parent_dir parent_dir;
    Sys.mkdir parent_dir
  end

let openfile fn =
  create_parent_dir fn;
  Out_channel.with_open_gen ... fn ...

Cheers,
Nicolas

2 Likes

If we are prepared to look outside the standard library then FileUtil.mkdir seems to do what create_parent_dir does.

It seems to have a lot of error-handling, but it may be doing some extra stuff I haven’t dug into.

This works very well, thank you!