2017-09-12 8 views
2

Posix.Process.execp을 사용하여 실행 한 명령의 출력을 캡처하려고합니다. stackoverflow 찾은 일부 C 코드를 포팅하고 한 실행에 대한 출력을 캡처 할 수 있지만 두 번째 실행 출력을 가져올 수 없습니다.SML에서 명령의 stdout 캡처

여기 내 함수의 :

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

여기에 해당 출력입니다 :

[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 
- 

어떤 제안은 내가 잘못 무엇에?

답변

1

내 초기 시도의 컴파일 된 출력 strace ./mwe을 실행하려고하는 것처럼

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

만들기

  1. 구성 :

    나는에 strace sml nwe.sml 2>&1 | grep -v getrusage를 실행하려고 파이프

  2. Setti 아이의 표준 출력을 겨하는 것은 부모의
  3. 설정 표준 입력 아마도 때문에 일부 인종, 파이프

이 주위에 두 번째 시간을 작동하지 않았다의 읽기 끝으로 파이프의 쓰기 종료 될 수 있습니다 조건 (strace -f 아래에서 실행한다는 것은 두 번째 파이프의 쓰기 끝 부분에 두 번째 자식 쓰기를 볼 수 있지만 부모는 두 번째 파이프의 읽기 끝에서 읽지 못하는 것을 의미했습니다). 나는이 접근법이 stdin을 clobbering하기 때문에 suboptimal이라는 것을 깨달았다.

내 officemate는 효과적으로 popen(3) 변종을 구현하려고했습니다. 더 좋은 방법은, 사실, popen를 구현하고 오히려 부모의 표준 입력/표준 출력을 건드리지보다, 파이프의 원하는 최종의 파일 기술자를 반환하는 것입니다. 또한 대칭입니다. 사용자가 파이프의 읽기 또는 쓰기 끝을 원하는지 지정할 수 있습니다. 여기에 내가 (피드백 환영) 해낸거야.

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; 

그리고 다음 출력은 다음과 같습니다 사이먼에

[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 

덕분에 strace를 실행하는 힌트에 대한 샤인.나는 아직도 내가 가진 것이 왜 효과가 없는지 아직 확신하지 못하고 있지만, 적어도 우리는 무슨 일이 일어나고 있는지 알고있다.

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 예 적응 것으로 보인다. 유일한 차이점은 fdopen(pipefd[0], "r")Posix.IO.dup2 { old = infd, new = Posix.FileSys.stdin }이라고 쓰는 C 행입니다.

이러한 결과가 실제로 나오는지 조사 할 수 있습니다. 각각의 프로그램에서 strace을 실행하고 시스템 호출이 언제 벗어나는 지 확인할 수 있습니다. 그래도 나는 더 이상 이해하지 못했습니다. 내가

#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

감사합니다. Simon! strace를 통해 두 번째 자식이 파일 설명자 N에 글을 쓰고 부모의 표준 입력이 dup2에 의해 N으로 설정되었지만 부모가 두 번째로 실제로 어떤 메시지도 읽지 않는다는 것을 알았습니다. 나는 더 상세한 분석을 게시 할 것이고 나는 그것을 어떻게 고쳐야 할 것인가. –