Racket:从交互式子流程输出读取所有可用数据

Racket:从交互式子流程输出读取所有可用数据,racket,Racket,我想使用子进程启动外部程序并监视其文本输出。我想检索在给定时间写入的所有数据,然后对其进行处理。然后,读取所有新数据并进行处理。这样做直到程序停止 #lang racket (match-define (list out in _ err _) (process* (find-executable-path "racket") "a.rkt")) (close-output-port in) (close-input-port err) (for/list ([

我想使用
子进程
启动外部程序并监视其文本输出。我想检索在给定时间写入的所有数据,然后对其进行处理。然后,读取所有新数据并进行处理。这样做直到程序停止

#lang racket

(match-define (list out in _ err _)
  (process* (find-executable-path "racket")
            "a.rkt"))

(close-output-port in)
(close-input-port err)

(for/list ([line (in-lines out)])
  (define len (string-length line))
  (printf "processed ~a chars\n" len)
  len)

(close-input-port out)
但是
读取
操作似乎一直都是
阻塞
。以下代码不起作用:

(let ((stream (process-stdout proc)))
  (for/list ( #:when (char-ready? stream)
             (line (in-lines stream)))
    line)))
是否有办法读取写入子流程标准输出上的所有新数据?

编辑:我添加了一个测试代码以避免误解

#lang racket

(struct process
  (stdout
   stdin))

(define (start-program exe)
  (define-values (s stdout stdin stderr) (subprocess #f #f #f exe))
  (thread (lambda () (copy-port stderr (current-error-port))))
  (process stdout stdin))

(define (send-to proc value)
   (write value  (process-stdin proc))
   (flush-output (process-stdin proc)))

(define (receive-from proc)
  (let ((stream (process-stdout proc)))        
    (for/list ((line (in-lines stream)))                        ;; TODO: seems blocking forever
      (printf "RECEIVE[~a] EOF=~a~%" line (eof-object? stream)) ;; EOF is never #f
      line)))

(define cmd-process (start-program "c:\\windows\\system32\\cmd.exe"))

(let loop ([x (receive-from cmd-process)])
  (for ((line x))
    (printf "RECEIVE:[~A]~%" x))
  (flush-output)
  ;;
  ;; TODO: how to detect that EVERYTHING has been read
  ;; so that I can input a command such as 'dir' or whatever
  ;;
  ;; I want to avoid parsing strings to detect that I cannot read anymore.
  ;;
  (loop (receive-from cmd-process)))

(close-input-port  (process-stdout cmd-process))
(close-output-port (process-stdin  cmd-process))
结果是:

RECEIVE[Microsoft Windows [Version 10.0.18362.535]] EOF=#f
RECEIVE[(c) 2019 Microsoft Corporation. All rights reserved.] EOF=#f
RECEIVE[] EOF=#f ;; <=== I want to get a #t here
RECEIVE[Microsoft Windows[Version 10.0.18362.535]]EOF=#f
接收[(c)2019 Microsoft Corporation。保留所有权利。]EOF=#f

接收[]EOF=#f 我看不出上面代码中的阻塞问题有多严重。看起来您希望程序为循环的每个迭代报告一些内容,但您的代码没有这样做

这里有一个例子,我认为它在做你想做的事情
a.rkt
打印10行:

()
(0)
(0 1)
...
(0 1 2 3 4 5 6 7 8)
每次打印延迟1秒

#lang racket

(for ([i 10])
  (writeln (build-list i values))
  (flush-output)
  (sleep 1))
现在,
b.rkt
将监视
a.rkt
的文本输出,“检索在给定时间写入的所有数据,然后对其进行处理。然后,读取所有新数据并对其进行处理。然后执行此操作,直到程序停止。”

上述程序输出:

processed 2 chars
processed 3 chars
processed 5 chars
processed 7 chars
processed 9 chars
processed 11 chars
processed 13 chars
processed 15 chars
processed 17 chars
processed 19 chars
并返回
”(2 3 5 7 9 11 13 15 17 19)
,其中每一行一可用就立即输出。就是每一秒


请注意,外部程序(
a.rkt
)需要刷新其输出。如果没有刷新,输出可以被缓冲,并且当输出仍然适合缓冲区时,您可能看不到“实时”输出。

我实际上找到了一种使用
事件解决问题的可能方法

#lang racket

(struct process
  (process
   stdout
   stdin
   read-event)) ;; add a read event

(define (start-program exe)
  (define-values (proc stdout stdin stderr) (subprocess #f #f #f exe))
  (thread (λ () (copy-port stderr (current-error-port))))
  (process proc stdout stdin (read-line-evt stdout 'any))) ;; create the event

(define (send-to proc value)
   (display value (process-stdin proc))
   (flush-output (process-stdin proc)))

(define (read-all)
  (let ((receive-string (sync/timeout 1 (process-read-event cmd-process)))) ;; read non-blocking
    (when (and receive-string (not (eof-object? receive-string)))
      (printf "RECV:[~A]~%" receive-string)
      (read-all))))

;; MAIN

(define cmd-process (start-program "c:\\windows\\system32\\cmd.exe"))

(read-all)
(printf "SEND:[DIR]~%")
(send-to cmd-process (format "DIR~A" #\newline))
(read-all)

(printf "[WAIT FOR TERMINATE]~%")
(printf "SEND:[exit]~%")
(send-to cmd-process (format "exit~A" #\newline))
(read-all)
(subprocess-wait (process-process cmd-process))
(printf "[WAIT FOR TERMINATE] ProcessStatus=~A~%"
  (subprocess-status (process-process cmd-process)))

(close-input-port  (process-stdout cmd-process))
(close-output-port (process-stdin  cmd-process))
此代码提供了预期的结果,无需解析字符串即可找到提示:

RECV:[Microsoft Windows [Version 10.0.18362.535]]
RECV:[(c) 2019 Microsoft Corporation. All rights reserved.]
RECV:[]
SEND:[DIR]
RECV:[E:\>DIR]
RECV:[ Volume in drive E has no label.]
RECV:[ Volume Serial Number is BE25-0CEB]
RECV:[]
RECV:[ Directory of E:\]
RECV:[]
RECV:[12/25/2019  01:18 PM    <DIR>          .]
RECV:[12/25/2019  01:18 PM    <DIR>          ..]
RECV:[               0 File(s)              0 bytes]
RECV:[               2 Dir(s)  163,789,271,040 bytes free]
RECV:[]
[WAIT FOR TERMINATE]
SEND:[exit]
RECV:[E:\>exit]
[WAIT FOR TERMINATE] ProcessStatus=0
RECV:[Microsoft Windows[Version 10.0.18362.535]]
记录:[(c)2019年微软公司。保留所有权利。]
记录:[]
发送:[目录]
记录:[E:\>目录]
RECV:[驱动器E中的卷没有标签。]
记录:[卷序列号为BE25-0CEB]
记录:[]
RECV:[电子目录:\]
记录:[]
记录:[2019年12月25日下午1:18]
记录:[2019年12月25日01:18下午..]
RECV:[0个文件0个字节]
RECV:[2个目录163789271040个可用字节]
记录:[]
[等待终止]
发送:[退出]
记录:[E:\>退出]
[等待终止]进程状态=0

如果有人找到一种方法来实现这种行为而不发生
事件
,我会接受答案。

实际上,这是一个交互式程序。提示前有一些文本需要阅读。然后你可以输入一些命令。然后你有一些命令和结果要读。等等我想知道是否有办法询问流是否从程序输出中收到了新的内容。问题之一是,一个shell命令echo 1与sleep链接10秒,chain echo 2与echo 1、enter和echo 2无法区分,除非您解析输出并查找提示。你能提供更多关于你想做什么的细节吗?您刚才添加的代码片段没有提供太多细节。
send to
是否会以编程方式调用?用户将如何与程序交互?Etc.>2除非您解析输出并查找提示符,否则这正是我想要避免的,因为所有程序的提示符都不同。您的变量
stream
是一个端口。它永远不会是对象的
eof?
,因此部分代码肯定是错误的。另外,
(for/list(#:when(char-ready?stream))
只会检查
(char-ready?stream)
一次。我认为这不是你想要的。有趣的评论。你是对的,代码现在对我来说似乎非常错误。我不知道
(char-ready?stream)
将被评估一次。这不是我想要的。