在SML中捕获命令的stdout

时间:2017-09-12 22:11:44

标签: posix sml smlnj

我试图使用.recv()捕获运行命令的输出。我移植了一些我在stackoverflow上找到的C代码并且可以捕获一次执行的输出,但是我无法获得输出以进行第二次执行。

这是我的功能:

Posix.Process.execp

以及相应的输出:

(* Runs a command c (command and argument list) using Posix.Process.execp. *)
(* If we successfully run the program, we return the lines output to stdout *)
(* in a list, along with SOME of the exit code. *)
(* If we fail to run the program, we return the error message in the list *)
(* and NONE. *)
fun execpOutput (c : string * string list) : (string list * Posix.Process.exit_status option) =
  let fun readAll () = case TextIO.inputLine TextIO.stdIn
                    of SOME s => s :: (readAll ())
                     | NONE => []
      (* Create a new pipe *)
      val { infd = infd, outfd = outfd } = Posix.IO.pipe ()
  in case Posix.Process.fork ()
      of NONE => (
      (* We are the child. First copy outfd to stdout; they will *)
      (* point to the same file descriptor and can be used interchangeably. *)
      (* See dup(2) for details. Then close infd: we don't need it and don't *)
      (* want to block because we have open file descriptors laying around *)
      (* when we want to exit. *)
      ( Posix.IO.dup2 { old = outfd, new = Posix.FileSys.stdout }
      ; Posix.IO.close infd
      ; Posix.Process.execp c )
      handle OS.SysErr (err, _) => ([err], NONE) )
       | SOME pid =>
     (* We are the parent. This time, copy infd to stdin, and get rid of the *)
     (* outfd we don't need. *)
     let val _ = ( Posix.IO.dup2 { old = infd, new = Posix.FileSys.stdin }
                 ; Posix.IO.close outfd )
         val (_, status) = Posix.Process.waitpid (Posix.Process.W_CHILD pid, [])
     in (readAll (), SOME status) end
  end

val lsls = (#1 (execpOutput ("ls", ["ls"]))) @ (#1 (execpOutput ("ls", ["ls"])))
val _ = app print lsls

关于我做错了什么的任何建议?

2 个答案:

答案 0 :(得分:2)

这不是一个完整的答案。

如果您以交互方式运行sml,您会注意到第一次通话后解释程序退出:

$ sml
Standard ML of New Jersey v110.81 [built: Wed May 10 21:25:32 2017]
- use "mwe.sml";
[opening mwe.sml]
...
- execpOutput ("ls", ["ls"]);
val it = (["mwe.sml\n"],SOME W_EXITED)
  : string list * ?.POSIX_Process.exit_status option
- 
$ # I didn't type exit ();

您的程序似乎是C中this pipe/fork/exec示例的改编版。唯一明显的区别是C行fdopen(pipefd[0], "r"),您可以在其中写Posix.IO.dup2 { old = infd, new = Posix.FileSys.stdin }

我可能会调查这些是否真的打算给出相同的结果。您可以在每个程序上运行strace,看看他们的系统调用何时偏离。但是,我还没有进一步意识到它。

我尝试在strace sml nwe.sml 2>&1 | grep -v getrusage上运行:

fun readAll () = case TextIO.inputLine TextIO.stdIn
                   of SOME s => s :: readAll ()
                    | NONE => []

fun execpOutput (c : string * string list) : (string list * Posix.Process.exit_status option) =
  let val { infd = infd, outfd = outfd } = Posix.IO.pipe ()
  in case Posix.Process.fork ()
          (* Child *)
       of NONE => (( Posix.IO.close infd
                   ; Posix.IO.dup2 { old = outfd, new = Posix.FileSys.stdout }
                   ; Posix.IO.dup2 { old = outfd, new = Posix.FileSys.stderr }
                   ; Posix.Process.execp c )
                  handle OS.SysErr (err, _) => ([err], NONE) )
         (* Parent *)
        | SOME pid =>
          let val _ = Posix.IO.close outfd
              val _ = Posix.IO.dup2 { old = infd, new = Posix.FileSys.stdin }
              val _ = Posix.Process.waitpid (Posix.Process.W_CHILD pid, [])
          in readAll () end
  end

val _ = app print (execpOutput ("ls", ["ls"]));
val _ = app print (execpOutput ("ls", ["ls"]));

就像我试图在

的编译输出上运行strace ./mwe一样
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>

void execpOutput(char *cmd[])
{
    pid_t pid;
    int pipefd[2];
    FILE* output;
    char line[256];
    int status;

    pipe(pipefd);
    pid = fork();
    if (pid == 0) {
        close(pipefd[0]);
        dup2(pipefd[1], STDOUT_FILENO);
        dup2(pipefd[1], STDERR_FILENO);
        execvp(cmd[0], cmd);
    } else {
        close(pipefd[1]);
        output = fdopen(pipefd[0], "r");
        while (fgets(line, sizeof(line), output)) {
            printf("%s", line);
        }
        waitpid(pid, &status, 0);
    }
}

int main(int argc, char *argv[])
{
    char *cmd[] = {"ls", NULL};

    execpOutput(cmd);
    execpOutput(cmd);
    return 2;
}

答案 1 :(得分:1)

我最初的尝试包括

  1. 创建管道
  2. 将子项的stdout设置为管道的写入端
  3. 将父级的stdin设置为管道的读取端
  4. 这第二次没有工作,也许是因为一些竞争条件(在strace -f下运行意味着我们可以看到第二个孩子写到第二个管道的写端,但是父从未设法从第二个管道的读取端读取)。我意识到这种方法也不是最理想的,因为它涉及破坏stdin。

    我的同事指出我正在有效地尝试实施popen(3)的变体。实际上,更好的方法是实现popen并返回管道所需末端的文件描述符,而不是破坏父级的stdin / stdout。它也是对称的,因为用户可以指定他们是否想要管道的读取或写入结束。这就是我提出的建议(欢迎反馈)。

    structure Popen :>
          sig
              (* Parent wants to write to stdin, read stdout, or read stdout + stderr *)
              datatype pipe_type = PIPE_W | PIPE_R | PIPE_RE
              val popen : string * pipe_type -> Posix.IO.file_desc
              val pclose : Posix.IO.file_desc -> Posix.Process.exit_status option
          end =
    struct
    
    datatype pipe_type = PIPE_W | PIPE_R | PIPE_RE
    
    type pinfo = { fd : Posix.ProcEnv.file_desc, pid : Posix.Process.pid }
    
    val pids : pinfo list ref = ref []
    
    (* Implements popen(3) *)
    fun popen (cmd, t) =
      let val { infd = readfd, outfd = writefd } = Posix.IO.pipe ()
      in case (Posix.Process.fork (), t)
          of (NONE, t) => (* Child *)
         (( case t
             of PIPE_W => Posix.IO.dup2 { old = readfd, new = Posix.FileSys.stdin }
              | PIPE_R => Posix.IO.dup2 { old = writefd, new = Posix.FileSys.stdout }
              | PIPE_RE => ( Posix.IO.dup2 { old = writefd, new = Posix.FileSys.stdout }
                           ; Posix.IO.dup2 { old = writefd, new = Posix.FileSys.stderr })
          ; Posix.IO.close writefd
          ; Posix.IO.close readfd
          ; Posix.Process.execp ("/bin/sh", ["sh", "-c", cmd]))
          handle OS.SysErr (err, _) =>
                 ( print ("Fatal error in child: " ^ err ^ "\n")
                 ; OS.Process.exit OS.Process.failure ))
           | (SOME pid, t) => (* Parent *)
         let val fd = case t of PIPE_W => (Posix.IO.close readfd; writefd)
                              | PIPE_R => (Posix.IO.close writefd; readfd)
                              | PIPE_RE => (Posix.IO.close writefd; readfd)
             val _ = pids := ({ fd = fd, pid = pid } :: !pids)
         in fd end
      end
    
    (* Implements pclose(3) *)
    fun pclose fd =
      case List.partition (fn { fd = f, pid = _ } => f = fd) (!pids)
       of ([], _) => NONE
        | ([{ fd = _, pid = pid }], pids') =>
          let val _ = pids := pids'
          val (_, status) = Posix.Process.waitpid (Posix.Process.W_CHILD pid, [])
          val _ = Posix.IO.close fd
          in SOME status end
        | _ => raise Bind (* This should be impossible. *)
    end
    
    val f = Popen.popen("ls", Popen.PIPE_R);
    val g = Popen.popen("read line; echo $line>/tmp/foo", Popen.PIPE_W);
    val _ = Posix.IO.writeVec (g, Word8VectorSlice.full (Byte.stringToBytes "Hello World! I was written by g\n"));
    val h = Popen.popen("cat /tmp/foo", Popen.PIPE_R);
    val i = Popen.popen("echo 'to stderr i' 1>&2", Popen.PIPE_R);
    val j = Popen.popen("echo 'to stderr j' 1>&2", Popen.PIPE_RE);
    val _ = app (fn fd => print (Byte.bytesToString (Posix.IO.readVec (fd, 1000)))) [f, h, i, j];
    val _ = map Popen.pclose [f, g, h, i, j];
    val _ = OS.Process.exit OS.Process.success;
    

    然后输出是:

    rak@zeta:~/popen$ rm /tmp/foo && ls && sml popen.sml
    popen.sml
    Standard ML of New Jersey v110.79 [built: Tue Aug  8 16:57:33 2017]
    [opening popen.sml]
    [autoloading]
    [library $SMLNJ-BASIS/basis.cm is stable]
    [library $SMLNJ-BASIS/(basis.cm):basis-common.cm is stable]
    [autoloading done]
    popen.sml:42.52 Warning: calling polyEqual
    structure Popen :
      sig
        datatype pipe_type = PIPE_R | PIPE_RE | PIPE_W
        val popen : string * pipe_type -> ?.POSIX_IO.file_desc
        val pclose : ?.POSIX_IO.file_desc -> ?.POSIX_Process.exit_status option
      end
    val f = FD {fd=4} : ?.POSIX_IO.file_desc
    val g = FD {fd=6} : ?.POSIX_IO.file_desc
    [autoloading]
    [autoloading done]
    val h = FD {fd=5} : ?.POSIX_IO.file_desc
    to stderr i
    val i = FD {fd=7} : ?.POSIX_IO.file_desc
    val j = FD {fd=8} : ?.POSIX_IO.file_desc
    popen.sml
    Hello World! I was written by g
    to stderr j
    

    感谢Simon Shine的暗示。我还不确定为什么我的工作没有用,但至少我们知道发生了什么。