2017-09-12 17 views
2

J'essaie de capturer la sortie d'une commande en utilisant Posix.Process.execp. J'ai porté un code C que j'ai trouvé sur stackoverflow et je peux capturer la sortie pour une exécution, mais je ne peux pas obtenir la sortie pour une seconde exécution.Capture de la sortie standard d'une commande dans SML

Voilà ma fonction:

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

et est ici la sortie correspondante:

[email protected]:/tmp/test$ ls 
a b c 
[email protected]:/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 
- 

Toutes les suggestions sur ce que je fais mal?

Répondre

1

Ma première tentative se composait de

  1. Création d'un tuyau
  2. Setti ng stdout de l'enfant à être la fin d'écriture du tube
  3. Réglage stdin du parent d'être la fin lecture du tube

Cela n'a pas fonctionné la deuxième fois, peut-être à cause d'une course condition (l'exécuter sous strace -f signifiait que nous pouvions voir le deuxième enfant écrire à l'extrémité d'écriture du deuxième canal, mais le parent n'a jamais réussi à lire depuis l'extrémité de lecture du deuxième canal). J'ai réalisé que cette approche est également sous-optimale car elle implique de taper stdin.

Mon collègue m'a fait remarquer que j'essayais effectivement d'implémenter une variante de popen(3). Une meilleure approche consiste, en fait, à implémenter popen et à renvoyer un descripteur de fichier pour l'extrémité souhaitée du canal, plutôt que d'écraser le stdin/stdout du parent. Il est également symétrique, en ce sens que l'utilisateur peut spécifier s'il veut lire ou écrire l'extrémité du tube. Voici ce que j'ai trouvé (commentaires bienvenus).

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; 

Et puis la sortie est:

[email protected]:~/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 

Merci à Simon Éclat pour l'indice d'exécuter strace.Je ne sais toujours pas pourquoi ce que j'ai eu ne fonctionne pas, mais au moins nous savons ce qui se passe.

2

Ceci n'est pas une réponse complète.

Si vous exécutez sml de manière interactive, vous remarquerez que l'interprète quitte après le premier appel:

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

Votre programme semble être une adaptation de this pipe/fork/exec exemple en C qui fonctionne. La seule différence apparente est la ligne C fdopen(pipefd[0], "r") où vous écrivez plutôt Posix.IO.dup2 { old = infd, new = Posix.FileSys.stdin }.

Je pourrais examiner si ceux qui sont vraiment destinés à donner le même résultat. Vous pouvez exécuter strace sur chacun des programmes et voir quand leurs appels système s'écartent. Mais je n'ai pas encore trouvé de sens.

J'ai essayé de courir strace sml nwe.sml 2>&1 | grep -v getrusage sur:

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"])); 

comme je l'ai essayé de courir strace ./mwe sur la sortie compilée de

#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

Merci Simon! Je l'ai couru à travers strace et a constaté que bien que le deuxième enfant écrivait à un descripteur de fichier N et stdin du parent a été mis à N par DUP2, le parent n'a jamais lu les messages la deuxième fois. Je posterai une analyse plus détaillée et comment je l'ai réparé demain. –