【问题标题】:How to capture subprocess output on both stderr and stdout in OCaml如何在 OCaml 中的 stderr 和 stdout 上捕获子进程输出
【发布时间】:2017-11-14 20:25:31
【问题描述】:

我正在查看以下 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)

应该如何解决这个问题? (更好的是,我应该在已经实现此功能的地方使用哪个库?)

【问题讨论】:

  • 我同意你的担忧。在我看来,您也可能出于类似原因在output_lines 中陷入僵局。
  • 顺便说一下,它会首先从stderr 读取,因为 OCaml 从右到左进行评估(虽然没有指定,但这是通常发生的情况)。这并不是说这样就可以解决死锁问题,仅供参考。
  • 谢谢。 (是的,output_lines 也是有问题的。幸运的是,我碰巧知道,在我的应用程序中,这里的数据会很小。)

标签: ocaml


【解决方案1】:

通常,您应该对一组描述符进行某种轮询以及非阻塞输入,以将信息从输出管道传输到输出。当然,这会使输出产生垃圾,即以任意顺序混合它。可以使用Unix.select 函数执行轮询。可以通过在打开文件时设置O_NONBLOCK 标志或使用Unix.set_nonblock 函数来启用非阻塞io。

说了这么多,我想强调的是,编写非阻塞代码并不是一件小事。特别是如果它是在原始的轮询/读/写循环中完成的。许多现代语言/运行时具有解耦此循环并以回调形式提供通用接口的库。 OCaml 是这个方向的先驱之一,它的Lwt 库。我们还有Async 库,它稍大但设计相同。我不会建议一个或另一个,因为它是基于意见的,但是,作为一个轶事,你的同名 Benjamin Pierceusing Lwt 对于他的 Unison 项目:)

没有 Lwt (Async) 的完整示例

我们可以从Unix System Programming 书中找到一个例子

(* 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 -> () ;;

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

【讨论】:

  • 谢谢。是的,就需要做的事情而言,我们意见一致。我什至开始写它,但是当我意识到 OCaml 中的 Unix.select 将文件描述符作为参数时感到沮丧,而 open_process_full 返回通道(除了管道中发生的任何缓冲之外,还包含 OCaml 端的内部缓冲)。当然,我也知道 Lwt。但我必须回去提醒自己这一切是如何运作的。所以我宁愿只是从某个地方窃取代码,或者找到一个可以做到这一点的库。
  • 啊,好的,我已经用一个完整的示例和对 OCaml Unix Programming Book 的详细描述的参考更新了这篇文章。此外,您应该使用 create_subrpocess 而不是 open_subprocess* 系列函数。
猜你喜欢
  • 2020-06-23
  • 1970-01-01
  • 2011-03-11
  • 2012-10-04
  • 2018-08-05
  • 2013-09-02
  • 2013-01-12
  • 1970-01-01
  • 2013-08-26
相关资源
最近更新 更多