我试图使用.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
关于我做错了什么的任何建议?
答案 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)
我最初的尝试包括
这第二次没有工作,也许是因为一些竞争条件(在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的暗示。我还不确定为什么我的工作没有用,但至少我们知道发生了什么。