# How to run external command from OCaml script

**URL:** <https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603>\
**Category:** Learning\
**Created:** [October 13, 2020, 5:31pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603 "2020-10-13T17:31:31Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![ffreling](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ffreling/32/2418_2.png) [@ffreling](https://discuss.ocaml.org/u/ffreling)\
**Post date:** [October 13, 2020, 5:31pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/1 "2020-10-13T17:31:31Z")

</div>

I’m trying to replace a bunch of bash script with OCaml but I have difficulties getting the output of external commands.

Here is my simple script:

```
#!/usr/bin/env utop

open Unix;;

let print_chan channel =
  let rec loop () =
      let () = print_endline (input_line channel) in
      loop ()
    in
  try loop ()
  with End_of_file -> close_in channel;;

let () =
  let (ocaml_stdout, ocaml_stdin, ocaml_stderr) = Unix.open_process_args_full "echo" [| "echo"; "foo" |] (Unix.environment ()) in
  close_out ocaml_stdin;
  print_chan ocaml_stdout;
  print_chan ocaml_stderr;
  print_endline "terminado!";

```

I am using utop as the toplevel to have access to common modules such as Unix.  
Unfortunately, my script does not print anything except “terminado!”. I’d like to get the output of `echo foo`.

---

<div class="post-metadata">

**Author:** ![zozozo](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/zozozo/32/59_2.png) [@zozozo](https://discuss.ocaml.org/u/zozozo)\
**Post date:** [October 13, 2020, 6:58pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/2 "2020-10-13T18:58:13Z")

</div>

I think you’re missing a call to `Unix.close_process_full` (cf [https://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html#VALclose\_process\_full](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Unix.html#VALclose_process_full) ) to wait for the termination of the command.

---

<div class="post-metadata">

**Author:** ![nojb](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/nojb/32/519_2.png) [@nojb](https://discuss.ocaml.org/u/nojb)\
**Post date:** [October 13, 2020, 7:23pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/3 "2020-10-13T19:23:22Z")

</div>

Hello,  
This is not directly related to your question, but for shell-style scripting, the combination of `Sys.command` + `Filename.quote_command` is a very robust and portable alternative to using `Unix`, and is simpler to use.

Cheers,  
Nicolas

---

<div class="post-metadata">

**Author:** ![ffreling](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ffreling/32/2418_2.png) [@ffreling](https://discuss.ocaml.org/u/ffreling)\
**Post date:** [October 13, 2020, 8:56pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/4 "2020-10-13T20:56:43Z")

</div>

I tried doing so but I get a `Exception: Sys_error "Bad file descriptor".`

```auto
let () =
  let ((ocaml_stdout, ocaml_stdin, ocaml_stderr) as p) = Unix.open_process_args_full "echo" [| "echo"; "foo" |] (Unix.environment ()) in
  let _ = Unix.close_process_full p in
  close_out ocaml_stdin;
  print_chan ocaml_stdout;
  print_chan ocaml_stderr;
  print_endline "terminado!";

```

---

<div class="post-metadata">

**Author:** ![ffreling](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ffreling/32/2418_2.png) [@ffreling](https://discuss.ocaml.org/u/ffreling)\
**Post date:** [October 13, 2020, 8:58pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/5 "2020-10-13T20:58:48Z")

</div>

I would like to avoid creating temporary files for this use case. I don’t mind the heavier API (once I get it working).

---

<div class="post-metadata">

**Author:** ![nojb](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/nojb/32/519_2.png) [@nojb](https://discuss.ocaml.org/u/nojb)\
**Post date:** [October 14, 2020, 2:38am UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/6 "2020-10-14T02:38:47Z")

</div>

> [@ffreling](#):
>
> I tried doing so but I get a `Exception: Sys_error "Bad file descriptor".`

`Unix.close_process_full` already takes care of closing the file descriptors, so you should not close them yourself.

Cheers,  
Nicolas

---

<div class="post-metadata">

**Author:** ![donn](https://avatars.discourse-cdn.com/v4/letter/d/7ea924/32.png) [@donn](https://discuss.ocaml.org/u/donn)\
**Post date:** [October 14, 2020, 2:58am UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/7 "2020-10-14T02:58:38Z")

</div>

I think it will work better if the first parameter (“command to run”) is a complete file path - “/bin/echo”.

That will get the example working. When you try to apply this as you intend, I’m guessing you will encounter new problems related to output buffering. You can address one of those on your end - where you write to the process, consider a flush at critical points, so the data actually becomes available to the process.

If this is simply a bulk data processing job, that may be all you need. If it’s intermittent data, then you have the same problem on the other end, where you will need access to the code to fix it - if the process output goes through C stdio or something like it, and doesn’t explicitly flush the buffer, it may not become available for a while.

The other pitfall here is that the pipe device itself is of limited size, and when it fills up, the process blocks. If you’re writing, you can’t read at the same time, so you fill up the pipe on your end, and you’re both blocked. And you have two outputs, stderr and stdin, so the process can block on one while you’re reading on the other.

You can address some of this with `Unix.select`, but bearing in mind that this operates on the underlying file descriptor and ignores buffered data - so there might be data that `input` could read, but `select` will say there’s nothing.

Temporary files are a very professional way to go.

---

<div class="post-metadata">

**Author:** ![ffreling](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ffreling/32/2418_2.png) [@ffreling](https://discuss.ocaml.org/u/ffreling)\
**Post date:** [October 14, 2020, 11:20am UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/8 "2020-10-14T11:20:20Z")

</div>

Even if I don’t close them myself I have this error:

```auto
 #!/usr/bin/env utop

open Unix;;

let print_chan channel =
  let rec loop () =
      let () = print_endline (input_line channel) in
      loop ()
    in
  try loop ()
  with End_of_file -> ();;

let () =
  let ((ocaml_stdout, ocaml_stdin, ocaml_stderr) as p) = Unix.open_process_args_full "echo" [| "echo"; "foo" |] (Unix.environment ()) in
  let _ = Unix.close_process_full p in
  print_chan ocaml_stdout;
  print_chan ocaml_stderr;
  print_endline "terminado!";

```

```auto
|> ./capture.ml
Exception: Sys_error "Bad file descriptor".

```

I guess once I close the process I can’t access the channels anymore.

---

<div class="post-metadata">

**Author:** ![ffreling](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/ffreling/32/2418_2.png) [@ffreling](https://discuss.ocaml.org/u/ffreling)\
**Post date:** [October 14, 2020, 11:46am UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/9 "2020-10-14T11:46:40Z")

</div>

Thanks! Specifying the whole binary path did fix the issue.  
The difference between `open_process` and `open_process_args` was not very clear in my head, but since I want to run commands in a shell, I should use `open_process`.

Here is my working code in case it can help someone:

```auto
#!/usr/bin/env utop

open Unix;;

let print_chan channel =
  let rec loop () =
      let () = print_endline (input_line channel) in
      loop ()
    in
  try loop ()
  with End_of_file -> close_in channel;;

let () =
  let (ocaml_stdout, ocaml_stdin, ocaml_stderr) = Unix.open_process_full "echo foo" [||] in
  close_out ocaml_stdin;
  print_chan ocaml_stdout;
  print_chan ocaml_stderr;
  print_endline "terminado!";

```

As for using temporary files, I don’t think I need it in my use case but you made good points about the pipe size limitation. I’ll keep it in mind if I run into pipe issues.

---

<div class="post-metadata">

**Author:** ![PatrickMacdonald](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/patrickmacdonald/32/2495_2.png) [@PatrickMacdonald](https://discuss.ocaml.org/u/PatrickMacdonald)\
**Post date:** [November 11, 2020, 8:04pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/10 "2020-11-11T20:04:56Z")

</div>

Good topic and excellent solutions, thanks all! 👍

---

<div class="post-metadata">

**Author:** ![chshersh](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/chshersh/32/4964_2.png) [@chshersh](https://discuss.ocaml.org/u/chshersh)\
**Post date:** [April 16, 2023, 2:00pm UTC](https://discuss.ocaml.org/t/how-to-run-external-command-from-ocaml-script/6603/11 "2023-04-16T14:00:06Z")

</div>

I found this topic when encountering a similar problem, and experienced several challenges I did not expect to face. Still, I found the discussion extremely helpful!

I’ve spent several hours trying to figure out things that were not documented and I haven’t found anywhere else, so I would like to share my findings to save someone else some time (especially beginners).

1. **Close channels only after reading them.**  
This is mentioned in the middle of the discussion but I wanted to highlight this separately.
2. **`Unix.open_process_full` doesn’t pass environment variables to the running process.**  
So if you expect the running command to have access to env variables like `$HOME`, you need to set them explicitly.
3. **Use `Unix.system` to run commands with all env variables passed and original output preserved.**  
If you don’t care about reading the output of the process, [the `Unix.system` function](https://v2.ocaml.org/api/Unix.html) is the easiest way to run external processes from an OCaml program.
4. **`Base` doesn’t have the `command` function in the `Sys` module.**  
Two modules in `base` and OCaml stdlib are different but still look the same. I’ve spent an unreasonable amount of time trying to figure out why I can use `Sys.command` when in fact I had `open Base`. Some StackOverflow answers mention this function for running external processes. Use `Unix.system` instead.
5. **You can’t preserve the original interleaved output of `stdout` and `stderr` when reading the process output.**  
Or at least, I haven’t found the way. But usually the process output text to both `stdout` and `stderr` in arbitrary order, and you can’t preserve this order if you reading from the corresponding handles independently.

* * *

Based on the above, I’ve implemented a small interface for running external processes and reading their output in [my recent tool](https://discuss.ocaml.org/t/ann-zbg-zero-bullshit-git/11929):

- [Interface](https://github.com/chshersh/zbg/blob/b9be80fa391a35f6ef20a4ff24af3a91c6d950fc/lib/process.mli)
- [Implementation](https://github.com/chshersh/zbg/blob/b9be80fa391a35f6ef20a4ff24af3a91c6d950fc/lib/process.ml)
- [Usage with passing env variables](https://github.com/chshersh/zbg/blob/b9be80fa391a35f6ef20a4ff24af3a91c6d950fc/lib/git.ml#L20-L27)
