With the Sys.command function I can run a shell command and get its exit code, but its output is dumped directly to standard out and standard error. With the Unix.open_process_args_full function I can capture the process’s standard output, standard input, and standard error as channels; however, it does not seem to provide a way to get the process’s exit code. How can I get both?
Note that from the Unix docs, a Unix_error is supposed to be thrown if the underlying process errors. However, this does not seem to happen for things like a command not being found:
try let _stdout, _stdin, _stderr = Unix.open_process_full "does_not_exist" [||] in
_stderr |> In_channel.input_all |> print_endline
with Unix.Unix_error (_errcode, _fn, _fnparam) -> print_endline "err"
Prints:
/bin/sh: line 1: does_not_exist: command not found
instead of err, which would have happened if it had thrown a Unix_error.
So I don’t know what to do if I want to both (1) check that a command can be executed without error and (2) not have its output vomited to stdout/stderr.
I’m not going to claim that I’ve done it, but …. looking at the code of the unix library, it appears that for each open_process_xx entry point, there is a close_process_xx entry point that you can use to get the status.
As mentioned already, you must use close_process_full in order to get the exit code. This function will block until the process exits. Apart from getting the exit code, calling close_process_full is necessary to avoid leaking the process handle.
Cheers,
Nicolas
if you don’t mind the extra dependency, you might enjoy containers’ CCUnix.call (or the other call* functions of the same module)
This is correct, if confusing. open_process_* functions pass their argument to the shell - so a Unix_error is raised if there is a problem executing /bin/sh (or whatever the shell happens to be). The error you’re seeing here is sh executing correctly and reporting an error in what it was asked to do.
The only way you can capture the output of Sys.command is to redirect it to files and then read those files afterwards - see Filename.quote_command which can be used to construct portable command lines for this (including handling 2>&1 correctly if you want stdout and stderr to the same file).
That said, Sys.command is something of a standard library wart, and it’s a rare or almost unique case where I’d assert that it’s just powerful enough to be used by the compiler, which has to be able to call auxiliary commands but doesn’t mind having to pipe things to temporary files, but is mostly useless in other scenarios.
Two choices - either you need to use one of the _args or create_process_* functions, but then you can’t use shell commands, or you need to do it in two stages and use command -v (that’s not portable to Windows, of course) to check that the command works and then execute it.
I was looking at Unix.open_process_args_full that you linked to. There I found Unix.process_full_pid which promises to return the PID from the aforementioned function. With that I guess you can call Unix.waitpid to wait and get the exit code.
I don’t think I have experience with the above combination, and I prefer to use a library such as bos if I need to do a lot of these things.