如何在OCaml中捕获stderr和stdout上的子流程输出

如何在OCaml中捕获stderr和stdout上的子流程输出,ocaml,Ocaml,我正在看下面的OCaml代码,它试图打开一个子流程,向它提供一些输入,然后收集它在stdout或stderr上生成的所有输出。但是,它首先从stdout读取所有内容,然后再从stderr读取所有内容这一事实似乎有点可疑——如果子进程碰巧向stderr写入了大量内容,那么结果似乎将是一个死锁 let rec output_lines (output : string list) (chan : out_channel) : unit = ignore (List.map (output_str

我正在看下面的OCaml代码,它试图打开一个子流程,向它提供一些输入,然后收集它在stdout或stderr上生成的所有输出。但是,它首先从stdout读取所有内容,然后再从stderr读取所有内容这一事实似乎有点可疑——如果子进程碰巧向stderr写入了大量内容,那么结果似乎将是一个死锁

let rec output_lines (output : string list) (chan : out_channel) : unit =
  ignore (List.map (output_string chan) output); flush chan

let async_command
    (name : string)
    (arguments : string list)
  : (in_channel * out_channel * in_channel) =
  Unix.open_process_full
    (name ^ " " ^ (String.concat " " arguments))
    (Unix.environment ())

let sync_command
    (name : string)
    (arguments : string list)
    (input : string list) : (string list * string list) =
  let (o, i, e) = async_command name arguments in
  output_lines input i;
  let out, err = (input_lines o, input_lines e) in
  let status = Unix.close_process_full (o, i, e) in
  begin match status with
    | Unix.WEXITED   x -> if x != 0 then raise (Shell_error (unlines err))
    | Unix.WSIGNALED x -> if x = Sys.sigint then raise Sys.Break
    | Unix.WSTOPPED  x -> if x = Sys.sigint then raise Sys.Break
  end;
  (out, err)

这应该如何解决?更好的是,在已经实现此功能的情况下,我应该使用什么库?

一般来说,您应该在一组描述符上使用某种轮询,同时使用非阻塞输入将信息从传输到输出。当然,这将垃圾化输出,即以任意顺序混合输出。可以使用Unix.select函数执行轮询。打开文件时,可以通过设置O_NONBLOCK标志或使用Unix.set_NONBLOCK函数来启用非阻塞io

说到这里,我想强调的是,编写非阻塞代码并不是一项简单的工作。尤其是在原始、轮询/读/写循环中执行时。许多现代语言/运行时都有一些库,它们将这个循环解耦,并以回调的形式提供一个通用接口。OCaml是这方面的先驱之一,它拥有自己的库。我们还有一个图书馆,稍微大一点,但设计相同。我不会给出这样或那样的建议,因为这是基于观点的,但是,作为一个轶事,你的同名者是Lwt的项目:

没有Lwt Async的完整示例 我们可以从书中找出一个例子


注意:您应该使用返回文件描述符的create_子流程来实现非阻塞IO。

我也有同感。在我看来,出于类似的原因,您也可以在输出_行中死锁。顺便说一句,当OCaml从右到左求值时,它将首先从stderr读取,虽然没有指定,但这是通常发生的情况。这并不是说这将解决死锁问题,只是供您参考。谢谢。是的,输出线也是有问题的。幸运的是,我碰巧知道,在我的应用程序中,这里的数据很小。谢谢。是的,我们在需要做的事情上意见一致。我甚至开始编写它,但当我意识到OCaml中的Unix.select将文件描述符作为参数,而open_process_full返回通道时,我感到沮丧,除了管道中发生的任何缓冲外,这些通道还包含OCaml端的内部缓冲。当然,我也知道Lwt。但我必须回去提醒自己这一切是如何运作的。所以我更愿意从某个地方偷取代码,或者找一个库来做。啊,好的,我已经更新了这篇文章,提供了一个完整的例子,并参考了OCaml Unix编程书,在书中详细描述了它。此外,您应该创建_子流程,而不是open_子流程*函数系列。
(* The multiplex function takes a descriptor open on the serial 
   port and two arrays of descriptors of the same size, one containing
   pipes connected to the standard input of the user processes, the other
   containing pipes connected to their standard output. *)
open Unix;;

let rec really_read fd buff start length =
  if length <= 0 then () else
    match read fd buff start length with
    | 0 -> raise End_of_file
    | n -> really_read fd buff (start+n) (length-n);;

let buffer = String.create 258;;

let multiplex channel inputs outputs =
  let input_fds = channel :: Array.to_list inputs in
  try
    while true do
      let (ready_fds, _, _) = select input_fds [] [] (-1.0) in
      for i = 0 to Array.length inputs - 1 do
        if List.mem inputs.(i) ready_fds then begin
          let n = read inputs.(i) buffer 2 255 in
          buffer.[0] <- char_of_int i;
          buffer.[1] <- char_of_int n;
          ignore (write channel buffer 0 (n+2));
          ()
        end
      done;
      if List.mem channel ready_fds then begin
        really_read channel buffer 0 2;
        let i = int_of_char(buffer.[0])
        and n = int_of_char(buffer.[1]) in
        if n = 0 then close outputs.(i) else
        begin
          really_read channel buffer 0 n;
          ignore (write outputs.(i) buffer 0 n);
          ()
        end
      end
    done
  with End_of_file -> () ;;