Stream 在Common Lisp中读取外部程序的二进制输出
我试图在SBCL中运行一个外部程序并捕获其输出。 输出是二进制数据(png图像),而SBCL坚持将其解释为字符串 我尝试了很多方法,比如Stream 在Common Lisp中读取外部程序的二进制输出,stream,common-lisp,sbcl,Stream,Common Lisp,Sbcl,我试图在SBCL中运行一个外部程序并捕获其输出。 输出是二进制数据(png图像),而SBCL坚持将其解释为字符串 我尝试了很多方法,比如 (trivial-shell:shell-command "/path/to/png-generator" :input "some input") (with-input-from-string (input "some input") (with-output-to-string (output) (run-program "/path/to
(trivial-shell:shell-command "/path/to/png-generator" :input "some input")
(with-input-from-string (input "some input")
(with-output-to-string (output)
(run-program "/path/to/png-generator" () :input input :output output))
(with-input-from-string (input "some input")
(flexi-streams:with-output-to-sequence (output)
(run-program "/path/to/png-generator" () :input input :output output))
但我会犯这样的错误
Illegal :UTF-8 character starting at byte position 0.
在我看来,SBCL试图将二进制数据解释为文本并对其进行解码。如何改变这种行为?我只对获取八位元向量感兴趣
编辑:由于上面的文字不清楚,我想补充一点,至少在flexi stream的情况下,流的元素类型是flexi streams:octect
(即(无符号字节8)
)。
我希望至少在这种情况下,
运行程序
读取原始字节时不会出现很多问题。相反,我收到了一条消息,如不知道如何复制到元素类型的流(无符号字节8)
编辑:我对无法完成这项非常简单的任务感到愤怒,并解决了问题
从功能上讲,将类型为UNSIGNED-BYTE的流发送到运行程序并使其正常工作的能力受到严重限制,原因我不明白。我尝试了灰色流、flexi流、fd流和其他一些机制,就像你一样
然而,仔细阅读运行程序的源代码(第五次或第六次),我注意到有一个选项:可以传递到输出的流。鉴于此,我想知道读取字节是否有效。。。的确如此。为了获得更高的性能,可以确定如何获取非文件流的长度并在其上运行读取序列
(let*
;; Get random bytes
((proc-var (sb-ext:run-program "head" '("-c" "10" "/dev/urandom")
:search t
;; let SBCL figure out the storage type. This is what solved the problem.
:output :stream))
;; Obtain the streams from the process object.
(output (process-output proc-var))
(err (process-error proc-var)))
(values
;;return both stdout and stderr, just for polish.
;; do a byte read and turn it into a vector.
(concatenate 'vector
;; A byte with value 0 is *not* value nil. Yay for Lisp!
(loop for byte = (read-byte output nil)
while byte
collect byte))
;; repeat for stderr
(concatenate 'vector
(loop for byte = (read-byte err nil)
while byte
collect byte))))
如果您愿意使用一些外部库,这可以通过babel streams实现。这是我用来从程序中安全获取内容的函数。我使用:latin-1,因为它只将前256个字节映射到字符。您可以删除字符串的八位字节并获得向量 如果您也想使用stderr,那么可以使用嵌套的“with output to sequence”来同时获得这两个属性
(defun safe-shell (command &rest args)
(octets-to-string
(with-output-to-sequence (stream :external-format :latin-1)
(let ((proc (sb-ext:run-program command args :search t :wait t :output stream)))
(case (sb-ext:process-status proc)
(:exited (unless (zerop (sb-ext:process-exit-code proc))
(error "Error in command")))
(t (error "Unable to terminate process")))))
:encoding :latin-1))
Paul Nathan已经给出了一个非常完整的答案,如何将程序中的I/O读取为二进制,因此我将添加为什么您的代码不起作用:因为您明确要求SBCL将I/O解释为UTF-8字符字符串,使用
和-{in,out}put to string
另外,我想指出的是,您不需要运行程序的源代码来获得解决方案。在中有明确的记录。是的,这似乎有效,非常感谢!无论如何,我不确定问题出在哪里。我的意思是,使用文件流作为输出效果很好,所以问题不完全在于运行程序,而在于字符串流和运行程序之间的交互。但是,我希望使用with output to sequence可以很好地工作。无论如何,至少我现在有了一个解决办法。再次感谢。@MarcoRighele:那么,如果你愿意接受答案,它会在SO系统中将问题标记为已回答-这是投票按钮旁的复选标记。if正在等待其他解决方案是否也起作用。无论如何,我更喜欢这个,因为它有较少的外部依赖性。我在运行您的示例时遇到问题。在linux下使用SBCL时,我得到一个警告:编码不是一个已知的参数关键字,而运行的安全shell给了我“未知字符编码:#”。我遗漏了什么吗?如果你不知道你使用的SBCL和babel的版本,我还不能完全确定。您也可以试试:iso-8859-1,因为这是它的标准名称。确保八位字节到字符串来自BABLE。啊,是的,我用sb ext:八位字节到字符串。有了正确的功能和最新版本的sbcl,它似乎工作正常。非常感谢。对于输出为字符串的
(其元素类型为字符
)当然是这样,但对于flexi-stream情况则不是这样,其中流由八位字节组成。我原以为run程序会根据流读取正确的元素类型的元素,但事实似乎并非如此。无论如何,我现在意识到这些示例不是很清楚,我将提供更多详细的结束错误消息,但您会注意到,flexi streams不会出现相同的错误。如果您查看错误消息和堆栈跟踪,您会发现一个合理的猜测是SBCL不使用任何写函数,而是使用一些特定于实现的优化,并且它在flexi流中失败。