I'm trying to capture the output of a command run using Posix.Process.execp
. I ported some C code I found on stackoverflow and can capture output for one execution, but I can't get the output for a second execution.
Here's my function:
(* 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
and here's the corresponding output:
rak@zeta:/tmp/test$ ls
a b c
rak@zeta:/tmp/test$ echo 'use "/tmp/mwe.sml";' | sml
Standard ML of New Jersey v110.79 [built: Tue Aug 8 16:57:33 2017]
- [opening /tmp/mwe.sml]
[autoloading]
[library $SMLNJ-BASIS/basis.cm is stable]
[library $SMLNJ-BASIS/(basis.cm):basis-common.cm is stable]
[autoloading done]
a
b
c
val execpOutput = fn
: string * string list -> string list * ?.POSIX_Process.exit_status option
val lsls = ["a\n","b\n","c\n"] : string list
val it = () : unit
-
Any suggestions on what I'm doing wrong?
This is not a complete answer.
If you run sml
interactively, you'll notice that the interpreter exits after the first call:
$ 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 ();
Your program appears to be an adaptation of this pipe/fork/exec example in C that does work. The only apparent difference is the C line fdopen(pipefd[0], "r")
where you instead write Posix.IO.dup2 { old = infd, new = Posix.FileSys.stdin }
.
I might investigate if those are really intended to give the same result. You could run strace
on each of the programs and see when their system calls deviate. I haven't made further sense of it, though.
I tried to run strace sml nwe.sml 2>&1 | grep -v getrusage
on:
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"]));
just as I tried to run strace ./mwe
on the compiled output of
#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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With