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

时间:2012-01-11 08:51:37

标签: 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/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(这是一个(unsigned-byte 8))。 我希望至少在这种情况下run-program能够读取原始字节而不会出现很多问题。相反,我收到了Don't know how to copy to stream of element-type (UNSIGNED-BYTE 8)

这样的消息

3 个答案:

答案 0 :(得分:4)

编辑:我因无法完成这项非常简单的任务而感到生气并解决了问题。

从功能上讲,将UNSIGNED-BYTE类型的流发送到运行程序并使其正常工作的能力受到严重限制,原因我不明白。我试过像你这样的灰色流,灵活流,fd流和其他一些机制。

然而,仔细阅读run-program的源代码(第五次或第六次),我注意到有一个选项:STREAM你可以传递给输出。鉴于此,我想知道读取字节是否有效......它确实如此。为了获得更高效的工作,可以确定如何获取非文件流的长度并在其上运行READ-SEQUENCE。

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

答案 1 :(得分:2)

如果您愿意使用某些外部库,可以使用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))                                                                                                                          

答案 2 :(得分:2)

Paul Nathan已经就如何从程序中读取I / O作为二进制文件给出了一个非常完整的答案,所以我只需添加为什么你的代码没有'工作:因为你明确要求 SBCL使用with-{in,out}put-to-string将I / O解释为UTF-8字符串。

另外,我想指出,您不需要像run-program的源代码那样到达解决方案。它在SBCL's manual中清楚地记录了。