SBCL程序I/O互操作性



我有一个程序要从中读取,比如说它的python。所以我有这两个功能:


(defun start-python ()
(let ((process 
(sb-ext:run-program "/usr/bin/python" nil
:output :stream
:input :stream
:wait nil
:search t
:error *standard-output*)))
process))
(defun test-process-stream ()
(let ((process (start-python)))
(format (sb-ext:process-input process) "print 'hello world!'~%")
(finish-output (sb-ext:process-input process))
;;I do not want to call "close" because I will be still reading from the input
(close (sb-ext:process-input process))
(print (read-line (sb-ext:process-output process)))
(when (listen (sb-ext:process-output process))
(print (read-line (sb-ext:process-output process))))
(close (sb-ext:process-output process))
(sb-ext:process-close process)
))

我希望能够增量读取python进程的输出,同时为其提供输入。我尝试了几种方法,甚至这里提到的方法:SBCL:在运行时收集运行程序过程的输出

但我没能在SBCL做到这一点。在示例代码中,我调用close,因为这是我获得任何输出的唯一方法。否则它就挂了。

如果有任何建议,我将不胜感激,因为我已经陷入了困境。我甚至尝试了(listen ...)(finish-output ...),但它仍然挂在(read-line ...)上。与(listen ...)的唯一区别是它返回false,并且不打印任何内容。在尝试阅读之前,我甚至尝试过(sleep 2)。还是什么都没有。

编辑:最终我的目标是运行swipl,它是SWI Prolog。我在这里用python作为例子。我想实现lisp和prolog之间的互操作性,这样我就可以向prolog发出查询并读取回响应。目前,我找不到任何适合我需求的项目或库,所以这就是我尝试这样做的原因。

[这个答案大部分都不有趣,因为提问者问Python时,他们说的是Prolog,所以我浪费了时间来解决他们说的问题,而不是他们实际遇到的问题。我把它留在这里,以防对其他人有用。]

我不认为这是SBCL的问题。给定以下代码:

(defun call-with-process (f program args &rest keys &key &allow-other-keys)
(let ((process (apply #'sb-ext:run-program program args keys)))
(unwind-protect
(funcall f process)
(sb-ext:process-close process))))
(defmacro with-process ((process program args &rest keys &key &allow-other-keys)
&body forms)
`(call-with-process
(lambda (,process)
,@forms)
,program ,args ,@keys))
(defun test-echo-process (&rest strings-to-send)
(with-process (p "/bin/cat" '()
:wait nil
:input ':stream
:output ':stream)
(let ((i (sb-ext:process-input p))
(o (sb-ext:process-output p)))
(dolist (s strings-to-send)
(format i "~A~%" s)
(finish-output i)
(format t "Sent ~A, got ~A~%" s (read-line o))))))

那么test-echo-process工作正常。

但这个功能(相当于你的)挂起了:

(defun test-python-process ()
(with-process (p "/usr/bin/python" '()
:wait nil
:input ':stream
:output ':stream)
(let ((i (sb-ext:process-input p))
(o (sb-ext:process-output p)))
(format i "print 'here'~%")
(finish-output i)
(format t "~A~%" (read-line o)))))

所以,实际上问题在于Python的行为方式。事实上,你可以证明这一点。以下是终端的一些输出:

$ cat | python
print "hi"
print "there"
hi
there
$

没有显示的是,在我键入第二个print命令后,我发送了一个EOF(即Unix上的^D)。

因此,我认为Python缓冲其输入是完全合理的,可能只是在它不是终端的情况下。

所以你需要做点什么来阻止这种情况的发生。作为最初的事情,我会把你想要Python运行的程序放在一个文件中,这样标准输入只做一件事。但你会发现自己身处一个痛苦的世界。

如果你实现这个功能

(defun test-python-script (args &rest strings-to-send)
(with-process (p "/usr/bin/python" args
:wait nil
:input ':stream
:output ':stream)
(let ((i (sb-ext:process-input p))
(o (sb-ext:process-output p)))
(dolist (s strings-to-send)
(format i "~A~%" s)
(finish-output i)
(format t "sent ~A, got ~A~%" s (read-line o))))))

然后你可能会认为echo.py中的一点Python是这样的:

from sys import stdin, exit
if __name__ == '__main__':
for l in stdin:
print "got " + l.strip()
exit(0)

&则运行CCD_ 11将起作用。但你至少在两个方面是错的(你可以通过在命令行上运行python echo.py并看到它仍然缓冲来检查这一点

第一种错误的方式是,在Python中使用文件作为迭代器已经内置了缓冲区,而这似乎是无法避免的。您可以通过编写一个无缓冲迭代器来处理这个问题,因此echo.py现在是

from sys import stdin, exit
class UnbufferedLineIterator(object):
# I take back what I said about 'perfectly reasonably'
def __init__(self, stream):
self.stream = stream
def __iter__(self):
return self
def next(self):
line = self.stream.readline()
if len(line) > 0:
return line
else:
raise StopIteration
if __name__ == '__main__':
for l in UnbufferedLineIterator(stdin):
print "got " + l.strip()
exit(0)

这可能有效,但不行,因为Python端的某个地方仍然有缓冲。通过运行带有-u参数的Python,可以用一种非常粗糙的方式来消除这种情况。所以,最后

* (test-python-script '("-u" "echo.py") "foo" "bar")
sent foo, got got foo
sent bar, got got bar

然而,我认为真正的答案是去问Python的人这是怎么回事,因为我不敢相信-u是正确的答案,也不敢相信它会这么难。

我用以下代码成功地实现了这一点:

(defun启动python()(let()process(sb ext:运行程序"/usr/bin/python3"nil:输出:流:输入:流:等待零:pty t:错误*标准输出*))过程)(取消读取直到换行(进程)(let((r"))(c的循环=(read char no hang(sb-ext:process pty process))do(progn(if(or(not c)(char=c#\newline))(从读取返回到换行符r)(setf r(concatenate‘string r(format nil"~c"c))))(取消打印所有输出(进程和键(丢弃零))(睡眠0.1)(循环do(progn(if(listen(sb ext:process pty process))(如果(不丢弃)(打印(读取到换行处理))(读到换行处理))(return))))(defun发送到python(进程str)(format(sb ext:process pty process)str)(完成输出(sb-ext:处理pty处理))(取消资助测试进程流()(let*((process(start-python)))(打印所有输出过程:丢弃t);;放弃横幅消息(发送到python进程"X=[1,2,3,4,5]~%print(X[:2],X[2:])~%X%%")(打印所有输出过程)(某人下一步:处理关闭过程)))

非常感谢@jkiiski帮助我调试这段代码。诀窍是使用:pty作为run-program的自变量,然后使用流(sb-ext:process-pty process)与进程通信。之后,执行(finish-output (sb-ext:process-pty process))将刷新我们对程序的输入。然后,等待一段时间以使子流程能够累积输出是至关重要的。在(sleep 0.1)之后,(listen ...)将能够判断是否有输出等待。然后它只是用(read-char-no-hang)循环读取字符,直到没有剩余字符为止。我用换行符分隔输出,如(read-until-newline)中所示上述代码产生以下输出:

">>> [1, 2] [3, 4, 5]^M" 
">>> [1, 2, 3, 4, 5]^M" 
">>> "

(print-all-output process)的任何后续调用都将递增地打印程序的输出。

最新更新