Stream 在Common Lisp中读取外部程序的二进制输出

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

我试图在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/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流中失败。