Unix.ProcRunning Subprocesses
These functions allow you to run a subprocess, possibly writing data to its standard input, and optionally reading back its standard output and / or standard error.
See the Tutorial.
Note that all these functions flush stdout and stderr to avoid duplicative output.
val runfull :
?env:string array ->
?usepath:bool ->
?err:(Stdlib.in_channel -> 'a) ->
?writer:(Stdlib.out_channel -> 'b) ->
?reader:(Stdlib.in_channel -> 'c) ->
string list ->
process_status * 'c option * 'a option(runfull ?env ?usepath ?err ?writer ?reader argv) runs the program designated by (hd argv) with (tl argv) as its command-line arguments.
argv is a raw argument vector; it is not passed to the shell, so its values do not need to be quoted, and there is no support for shell metacharacters such as I/O redirection or pipes. (See Shell for simple-minded pipeline support.)
If ~writer is provided, it is called to write data to the process's standard input.
If ~reader is provided, it is called to read data from the process's standard output.
If ~err is provided, it is called to read data from the process's standard error.
The return value is a triple consisting of:
process_status~reader, if any, or None if ~reader was not provided~err, if any, or None if ~err was not providedIf given, ~env is used as the environment; the default is to use the value of (environment ()); ~env must be in the same format.
~usepath determines whether or not (hd argv) is searched in PATH; default: true
If (argv = []), Invalid_argument is raised.
Note that if the process produces output to either stdout or stderr, and you don't read it (via ~reader or ~err), the process status will be (WSIGNALED (-8)), indicating SIGPIPE.
val run :
?oknon0:bool ->
?env:string array ->
?usepath:bool ->
?err:(Stdlib.in_channel -> 'a) ->
?writer:(Stdlib.out_channel -> 'b) ->
?reader:(Stdlib.in_channel -> 'c) ->
string list ->
'c option * 'a optionrun is equivalent to runfull except it returns only the stdout and stderr values.
It does not return the process status.
If the process exits with a non-zero WEXITED status, Failure is raised, unless ~oknon0:true (default: false).
Failure is also raised if the process was killed or stopped by a signal.
val readwith :
?oknon0:bool ->
?env:string array ->
?usepath:bool ->
(Stdlib.in_channel -> 'a) ->
string list ->
'areadwith is equivalent to run except it returns only the stdout value.
read is (readwith ~reader:Prelude.read).
readline ?oknon0 ?env ?usepath argv is specially designed to read one line from stdin.
It reads only one line, and ignores any SIGPIPE from the process.
Example: readline ["emacs"; "--version"] would return something like "GNU Emacs 28.2" without the additional lines explaining the copyright and lack of warranty.
readlines is (readwith ~reader:Prelude.readlines).
(write ?oknon0 ?env ?usepath argv str) writes str to the stdin of the subprocess.
All optional parameters are as for run.
This is useful for programs that read stdin and produce no output. Example:
write ["mail"; "-s"; "Your mail is here"; "luser@example.com"] msgsends mail to the user; msg would be a string containing an RFC-822 email message, complete with headers and body.
val writelines :
?oknon0:bool ->
?env:string array ->
?usepath:bool ->
string list ->
string list ->
unit(writelines argv) is (String.concat "\n" $ write argv).
All optional parameters are as for run.
(rw ?oknon0 ?env ?usepath argv str) writes str to the process specified by argv and returns the process's stdout.
All optional parameters are as for run.
Example: rw ["tr"; "a-z"; "A-Z"] "ocaml" is an expensive way to get the value "OCAML".
val rwlines :
?oknon0:bool ->
?env:string array ->
?usepath:bool ->
string list ->
string list ->
string(rwlines ?oknon0 ?env ?usepath argv lines) writes the lines to the process specified by argv and returns the process's output.
All optional parameters are as for run.
Suitable for ~reader and ~err.
(reader chan) is Prelude.read and thus returns all the text from chan.
reader is suitable for a ~reader parameter; because of the pun, when using a local open this shorthand:
Unix.Proc.(runfull ~reader ["date"])is equivalent to:
Unix.Proc.(runfull ~reader:reader ["date"])and thus also to:
Unix.Proc.runfull ~reader:Prelude.read ["date"](err chan) is Prelude.read and thus returns all the text from chan.
This function is useful for local opens as for reader above.
(sink chan) reads all data from chan and discards it.
This function is suitable for use with ~reader or ~err in order to avoid a SIGPIPE signal.
Suitable for ~writer.
(string str chan) writes str to the process via chan.
(lines list chan) writes each element of list to the process via chan, adding a terminating newline to each element.
(channel ic oc) writes the data on the input channel ic to the process via oc.
(file fn oc) writes the contents of the file fn to the process via oc.
val status : (process_status * 'a * 'b) -> intstatus converts the triple return value of runfull to the exit status, i.e. the value n of process status (WEXITED n).
If the process status is either WSIGNALED or WSTOPPED, Failure is raised.
val explanation : (process_status * 'a * string option) -> string * stringexplanation returns a pair of explanations for the termination of the proces.
The first explanation is short, the second may be longer (possibly multiple lines).
val successful : (process_status * 'a * 'b) -> boolsuccessful converts the triple return value of runfull to true if it contains WEXITED 0 and false otherwise.
stdout converts the triple return value of runfull to the stdout value.
stderr converts the triple return value of runfull to the stderr value.
The most common use case is probably to read data back from the process's stdout; you would typically use one of read, readline, or readlines.
read returns all of the process's stdout as a string:
read ["date";"+%Y"] = "2018\n"read ["ocamlc";"-v"] = "The OCaml compiler, version 4.06.1\nStandard library directory: /usr/app/lib/opam/4.06.1/lib/ocaml\n"Sometimes you know the subprocess will only return a single line; in this case, you might use readline to save you from having to trim a trailing newline:
readline ["date";"+%Y"] = "2018"readline can also be used to simulate piping the process's stdout to head -1:
readline ["ocamlc";"-v"]
= "The OCaml compiler, version 4.06.1"readlines returns all of the process's stdout as a list of lines:
readlines [ "ocamlc";"-v" ] = ["The OCaml compiler, version 4.06.1";
"Standard library directory: /usr/app/lib/opam/4.06.1/lib/ocaml"]read ["ocamlc";"-v"] |> split ~sep:"\n" = readlines ["ocamlc";"-v"]Sometimes you only need to write data to a process; we can send an email with write:
Unix.Proc.write ["mailx"; "-s"; "Email from OCaml"; "keith"] "Greetings from OCaml!"writelines works analogously:
1--10 |> map string_of_int |> Unix.Proc.writelines ["mailx"; "-s"; "Some Integers"; "keith"]Sometimes you need to both write data to a process and then read data back from it. You can do this with rw and rwlines:
rw [ "tr"; "a-z"; "A-Z" ] "foobar" = "FOOBAR"1--10 |> map string_of_int |> Unix.Proc.rwlines [ "wc"; "-l" ] = "10\n"runIn order to avoid reading all of a potentially large amount of data into memory, you can do all your processing inside ~reader:
1099 = (run ~reader:(foldlines (fun n _ -> succ n) 0) ["curl"; "-Ls"; "https://www2.lib.uchicago.edu/keith/software/prelude/Prelude.html"] |> fst |> Option.get)runSometimes you want to see the stderr output of a process as well the stdout, perhaps so that you can display it when there's an error:
match run ~oknon0:true ~err:Prelude.read ["ocamlc";"-c";"foo.ml"] with
| None, Some "" -> print "compilation successful"
| None, Some err -> print err runfullrunfull is just like run but also returns the process state, so you can examine the exit status or detect that the process was killed by a signal.
runfull ["true"] = (WEXITED 0, None, None)The status function will extract just the exit status:
runfull ["false"] |> status = 1Whenever you interact with a process that produces output to stdout or stderr, if you don't read all of that output, you will get a SIGPIPE exception. For example, /usr/share/dict/words is 1,185,564 bytes on my system; this command looks like a reasonable way to get the first line of the file:
Unix.Proc.run ~reader:readline ["cat"; "/usr/share/dict/words"] but in fact it raises SIGPIPE, because we neglected to read the rest of the output.
To solve this problem, you need to either read all the output (which may be inefficient), or ignore the SIGPIPE.
The readline function takes care of ignoring SIGPIPE for you.